2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154 void InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166 /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177 char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179 int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
240 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
245 int lastSavedGame; /* [HGM] save: ID of game */
247 /* States for ics_getting_history */
249 #define H_REQUESTED 1
250 #define H_GOT_REQ_HEADER 2
251 #define H_GOT_UNREQ_HEADER 3
252 #define H_GETTING_MOVES 4
253 #define H_GOT_UNWANTED_HEADER 5
255 /* whosays values for GameEnds */
264 /* Maximum number of games in a cmail message */
265 #define CMAIL_MAX_GAMES 20
267 /* Different types of move when calling RegisterMove */
269 #define CMAIL_RESIGN 1
271 #define CMAIL_ACCEPT 3
273 /* Different types of result to remember for each game */
274 #define CMAIL_NOT_RESULT 0
275 #define CMAIL_OLD_RESULT 1
276 #define CMAIL_NEW_RESULT 2
278 /* Telnet protocol constants */
289 static char * safeStrCpy( char * dst, const char * src, size_t count )
291 assert( dst != NULL );
292 assert( src != NULL );
295 strncpy( dst, src, count );
296 dst[ count-1 ] = '\0';
301 //[HGM] for future use? Conditioned out for now to suppress warning.
302 static char * safeStrCat( char * dst, const char * src, size_t count )
306 assert( dst != NULL );
307 assert( src != NULL );
310 dst_len = strlen(dst);
312 assert( count > dst_len ); /* Buffer size must be greater than current length */
314 safeStrCpy( dst + dst_len, src, count - dst_len );
320 /* Some compiler can't cast u64 to double
321 * This function do the job for us:
323 * We use the highest bit for cast, this only
324 * works if the highest bit is not
325 * in use (This should not happen)
327 * We used this for all compiler
330 u64ToDouble(u64 value)
333 u64 tmp = value & u64Const(0x7fffffffffffffff);
334 r = (double)(s64)tmp;
335 if (value & u64Const(0x8000000000000000))
336 r += 9.2233720368547758080e18; /* 2^63 */
340 /* Fake up flags for now, as we aren't keeping track of castling
341 availability yet. [HGM] Change of logic: the flag now only
342 indicates the type of castlings allowed by the rule of the game.
343 The actual rights themselves are maintained in the array
344 castlingRights, as part of the game history, and are not probed
350 int flags = F_ALL_CASTLE_OK;
351 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
352 switch (gameInfo.variant) {
354 flags &= ~F_ALL_CASTLE_OK;
355 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
356 flags |= F_IGNORE_CHECK;
358 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
361 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
363 case VariantKriegspiel:
364 flags |= F_KRIEGSPIEL_CAPTURE;
366 case VariantCapaRandom:
367 case VariantFischeRandom:
368 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
369 case VariantNoCastle:
370 case VariantShatranj:
372 flags &= ~F_ALL_CASTLE_OK;
380 FILE *gameFileFP, *debugFP;
383 [AS] Note: sometimes, the sscanf() function is used to parse the input
384 into a fixed-size buffer. Because of this, we must be prepared to
385 receive strings as long as the size of the input buffer, which is currently
386 set to 4K for Windows and 8K for the rest.
387 So, we must either allocate sufficiently large buffers here, or
388 reduce the size of the input buffer in the input reading part.
391 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
392 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
393 char thinkOutput1[MSG_SIZ*10];
395 ChessProgramState first, second;
397 /* premove variables */
400 int premoveFromX = 0;
401 int premoveFromY = 0;
402 int premovePromoChar = 0;
404 Boolean alarmSounded;
405 /* end premove variables */
407 char *ics_prefix = "$";
408 int ics_type = ICS_GENERIC;
410 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
411 int pauseExamForwardMostMove = 0;
412 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
413 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
414 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
415 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
416 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
417 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
418 int whiteFlag = FALSE, blackFlag = FALSE;
419 int userOfferedDraw = FALSE;
420 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
421 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
422 int cmailMoveType[CMAIL_MAX_GAMES];
423 long ics_clock_paused = 0;
424 ProcRef icsPR = NoProc, cmailPR = NoProc;
425 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
426 GameMode gameMode = BeginningOfGame;
427 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
428 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
429 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
430 int hiddenThinkOutputState = 0; /* [AS] */
431 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
432 int adjudicateLossPlies = 6;
433 char white_holding[64], black_holding[64];
434 TimeMark lastNodeCountTime;
435 long lastNodeCount=0;
436 int have_sent_ICS_logon = 0;
438 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
439 long timeControl_2; /* [AS] Allow separate time controls */
440 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
441 long timeRemaining[2][MAX_MOVES];
443 TimeMark programStartTime;
444 char ics_handle[MSG_SIZ];
445 int have_set_title = 0;
447 /* animateTraining preserves the state of appData.animate
448 * when Training mode is activated. This allows the
449 * response to be animated when appData.animate == TRUE and
450 * appData.animateDragging == TRUE.
452 Boolean animateTraining;
458 Board boards[MAX_MOVES];
459 /* [HGM] Following 7 needed for accurate legality tests: */
460 char epStatus[MAX_MOVES];
461 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
462 char castlingRank[BOARD_SIZE]; // and corresponding ranks
463 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
464 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
465 int initialRulePlies, FENrulePlies;
467 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
471 ChessSquare FIDEArray[2][BOARD_SIZE] = {
472 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475 BlackKing, BlackBishop, BlackKnight, BlackRook }
478 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
479 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482 BlackKing, BlackKing, BlackKnight, BlackRook }
485 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
486 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488 { BlackRook, BlackMan, BlackBishop, BlackQueen,
489 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
492 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
493 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
494 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
496 BlackKing, BlackBishop, BlackKnight, BlackRook }
499 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
508 ChessSquare ShogiArray[2][BOARD_SIZE] = {
509 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
510 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
511 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
512 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
515 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
516 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
517 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
519 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
523 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
526 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
529 ChessSquare GreatArray[2][BOARD_SIZE] = {
530 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
531 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
532 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
533 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
536 ChessSquare JanusArray[2][BOARD_SIZE] = {
537 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
538 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
539 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
540 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
544 ChessSquare GothicArray[2][BOARD_SIZE] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
546 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
548 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
551 #define GothicArray CapablancaArray
555 ChessSquare FalconArray[2][BOARD_SIZE] = {
556 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
557 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
559 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
562 #define FalconArray CapablancaArray
565 #else // !(BOARD_SIZE>=10)
566 #define XiangqiPosition FIDEArray
567 #define CapablancaArray FIDEArray
568 #define GothicArray FIDEArray
569 #define GreatArray FIDEArray
570 #endif // !(BOARD_SIZE>=10)
573 ChessSquare CourierArray[2][BOARD_SIZE] = {
574 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
575 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
577 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
579 #else // !(BOARD_SIZE>=12)
580 #define CourierArray CapablancaArray
581 #endif // !(BOARD_SIZE>=12)
584 Board initialPosition;
587 /* Convert str to a rating. Checks for special cases of "----",
589 "++++", etc. Also strips ()'s */
591 string_to_rating(str)
594 while(*str && !isdigit(*str)) ++str;
596 return 0; /* One of the special "no rating" cases */
604 /* Init programStats */
605 programStats.movelist[0] = 0;
606 programStats.depth = 0;
607 programStats.nr_moves = 0;
608 programStats.moves_left = 0;
609 programStats.nodes = 0;
610 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
611 programStats.score = 0;
612 programStats.got_only_move = 0;
613 programStats.got_fail = 0;
614 programStats.line_is_book = 0;
620 int matched, min, sec;
622 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
624 GetTimeMark(&programStartTime);
625 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
628 programStats.ok_to_send = 1;
629 programStats.seen_stat = 0;
632 * Initialize game list
638 * Internet chess server status
640 if (appData.icsActive) {
641 appData.matchMode = FALSE;
642 appData.matchGames = 0;
644 appData.noChessProgram = !appData.zippyPlay;
646 appData.zippyPlay = FALSE;
647 appData.zippyTalk = FALSE;
648 appData.noChessProgram = TRUE;
650 if (*appData.icsHelper != NULLCHAR) {
651 appData.useTelnet = TRUE;
652 appData.telnetProgram = appData.icsHelper;
655 appData.zippyTalk = appData.zippyPlay = FALSE;
658 /* [AS] Initialize pv info list [HGM] and game state */
662 for( i=0; i<MAX_MOVES; i++ ) {
663 pvInfoList[i].depth = -1;
665 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
670 * Parse timeControl resource
672 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
673 appData.movesPerSession)) {
675 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
676 DisplayFatalError(buf, 0, 2);
680 * Parse searchTime resource
682 if (*appData.searchTime != NULLCHAR) {
683 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
685 searchTime = min * 60;
686 } else if (matched == 2) {
687 searchTime = min * 60 + sec;
690 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
691 DisplayFatalError(buf, 0, 2);
695 /* [AS] Adjudication threshold */
696 adjudicateLossThreshold = appData.adjudicateLossThreshold;
698 first.which = "first";
699 second.which = "second";
700 first.maybeThinking = second.maybeThinking = FALSE;
701 first.pr = second.pr = NoProc;
702 first.isr = second.isr = NULL;
703 first.sendTime = second.sendTime = 2;
704 first.sendDrawOffers = 1;
705 if (appData.firstPlaysBlack) {
706 first.twoMachinesColor = "black\n";
707 second.twoMachinesColor = "white\n";
709 first.twoMachinesColor = "white\n";
710 second.twoMachinesColor = "black\n";
712 first.program = appData.firstChessProgram;
713 second.program = appData.secondChessProgram;
714 first.host = appData.firstHost;
715 second.host = appData.secondHost;
716 first.dir = appData.firstDirectory;
717 second.dir = appData.secondDirectory;
718 first.other = &second;
719 second.other = &first;
720 first.initString = appData.initString;
721 second.initString = appData.secondInitString;
722 first.computerString = appData.firstComputerString;
723 second.computerString = appData.secondComputerString;
724 first.useSigint = second.useSigint = TRUE;
725 first.useSigterm = second.useSigterm = TRUE;
726 first.reuse = appData.reuseFirst;
727 second.reuse = appData.reuseSecond;
728 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
729 second.nps = appData.secondNPS;
730 first.useSetboard = second.useSetboard = FALSE;
731 first.useSAN = second.useSAN = FALSE;
732 first.usePing = second.usePing = FALSE;
733 first.lastPing = second.lastPing = 0;
734 first.lastPong = second.lastPong = 0;
735 first.usePlayother = second.usePlayother = FALSE;
736 first.useColors = second.useColors = TRUE;
737 first.useUsermove = second.useUsermove = FALSE;
738 first.sendICS = second.sendICS = FALSE;
739 first.sendName = second.sendName = appData.icsActive;
740 first.sdKludge = second.sdKludge = FALSE;
741 first.stKludge = second.stKludge = FALSE;
742 TidyProgramName(first.program, first.host, first.tidy);
743 TidyProgramName(second.program, second.host, second.tidy);
744 first.matchWins = second.matchWins = 0;
745 strcpy(first.variants, appData.variant);
746 strcpy(second.variants, appData.variant);
747 first.analysisSupport = second.analysisSupport = 2; /* detect */
748 first.analyzing = second.analyzing = FALSE;
749 first.initDone = second.initDone = FALSE;
751 /* New features added by Tord: */
752 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
753 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
754 /* End of new features added by Tord. */
755 first.fenOverride = appData.fenOverride1;
756 second.fenOverride = appData.fenOverride2;
758 /* [HGM] time odds: set factor for each machine */
759 first.timeOdds = appData.firstTimeOdds;
760 second.timeOdds = appData.secondTimeOdds;
762 if(appData.timeOddsMode) {
763 norm = first.timeOdds;
764 if(norm > second.timeOdds) norm = second.timeOdds;
766 first.timeOdds /= norm;
767 second.timeOdds /= norm;
770 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
771 first.accumulateTC = appData.firstAccumulateTC;
772 second.accumulateTC = appData.secondAccumulateTC;
773 first.maxNrOfSessions = second.maxNrOfSessions = 1;
776 first.debug = second.debug = FALSE;
777 first.supportsNPS = second.supportsNPS = UNKNOWN;
780 first.optionSettings = appData.firstOptions;
781 second.optionSettings = appData.secondOptions;
783 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
784 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
785 first.isUCI = appData.firstIsUCI; /* [AS] */
786 second.isUCI = appData.secondIsUCI; /* [AS] */
787 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
788 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
790 if (appData.firstProtocolVersion > PROTOVER ||
791 appData.firstProtocolVersion < 1) {
793 sprintf(buf, _("protocol version %d not supported"),
794 appData.firstProtocolVersion);
795 DisplayFatalError(buf, 0, 2);
797 first.protocolVersion = appData.firstProtocolVersion;
800 if (appData.secondProtocolVersion > PROTOVER ||
801 appData.secondProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.secondProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 second.protocolVersion = appData.secondProtocolVersion;
810 if (appData.icsActive) {
811 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
812 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
813 appData.clockMode = FALSE;
814 first.sendTime = second.sendTime = 0;
818 /* Override some settings from environment variables, for backward
819 compatibility. Unfortunately it's not feasible to have the env
820 vars just set defaults, at least in xboard. Ugh.
822 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
827 if (appData.noChessProgram) {
828 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829 sprintf(programVersion, "%s", PACKAGE_STRING);
834 while (*q != ' ' && *q != NULLCHAR) q++;
836 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
837 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
838 sprintf(programVersion, "%s + ", PACKAGE_STRING);
839 strncat(programVersion, p, q - p);
841 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
842 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
843 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantBerolina: /* might work if TestLegality is off */
903 case VariantCapaRandom: /* should work */
904 case VariantJanus: /* should work */
905 case VariantSuper: /* experimental */
906 case VariantGreat: /* experimental, requires legality testing to be off */
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
912 InitEngineUCI( installDir, &second );
915 int NextIntegerFromString( char ** str, long * value )
920 while( *s == ' ' || *s == '\t' ) {
926 if( *s >= '0' && *s <= '9' ) {
927 while( *s >= '0' && *s <= '9' ) {
928 *value = *value * 10 + (*s - '0');
940 int NextTimeControlFromString( char ** str, long * value )
943 int result = NextIntegerFromString( str, &temp );
946 *value = temp * 60; /* Minutes */
949 result = NextIntegerFromString( str, &temp );
950 *value += temp; /* Seconds */
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
959 int result = -1; long temp, temp2;
961 if(**str != '+') return -1; // old params remain in force!
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
966 /* time only: incremental or sudden-death time control */
967 if(**str == '+') { /* increment follows; read it */
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
972 *moves = 0; *tc = temp * 1000;
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
976 (*str)++; /* classical time control */
977 result = NextTimeControlFromString( str, &temp2);
986 int GetTimeQuota(int movenr)
987 { /* [HGM] get time to add from the multi-session time-control string */
988 int moves=1; /* kludge to force reading of first session */
989 long time, increment;
990 char *s = fullTimeControlString;
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996 if(movenr == -1) return time; /* last move before new session */
997 if(!moves) return increment; /* current session is incremental */
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
999 } while(movenr >= -1); /* try again for next session */
1001 return 0; // no new time quota on this move
1005 ParseTimeControl(tc, ti, mps)
1011 int matched, min, sec;
1013 matched = sscanf(tc, "%d:%d", &min, &sec);
1015 timeControl = min * 60 * 1000;
1016 } else if (matched == 2) {
1017 timeControl = (min * 60 + sec) * 1000;
1026 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1029 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1030 else sprintf(buf, "+%s+%d", tc, ti);
1033 sprintf(buf, "+%d/%s", mps, tc);
1034 else sprintf(buf, "+%s", tc);
1036 fullTimeControlString = StrSave(buf);
1038 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1043 /* Parse second time control */
1046 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1054 timeControl_2 = tc2 * 1000;
1064 timeControl = tc1 * 1000;
1068 timeIncrement = ti * 1000; /* convert to ms */
1069 movesPerSession = 0;
1072 movesPerSession = mps;
1080 if (appData.debugMode) {
1081 fprintf(debugFP, "%s\n", programVersion);
1084 if (appData.matchGames > 0) {
1085 appData.matchMode = TRUE;
1086 } else if (appData.matchMode) {
1087 appData.matchGames = 1;
1089 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1090 appData.matchGames = appData.sameColorGames;
1091 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1092 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1093 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1096 if (appData.noChessProgram || first.protocolVersion == 1) {
1099 /* kludge: allow timeout for initial "feature" commands */
1101 DisplayMessage("", _("Starting chess program"));
1102 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1107 InitBackEnd3 P((void))
1109 GameMode initialMode;
1113 InitChessProgram(&first, startedFromSetupPosition);
1116 if (appData.icsActive) {
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
1123 if (*appData.icsCommPort != NULLCHAR) {
1124 sprintf(buf, _("Could not open comm port %s"),
1125 appData.icsCommPort);
1127 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1128 appData.icsHost, appData.icsPort);
1130 DisplayFatalError(buf, err, 1);
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138 } else if (appData.noChessProgram) {
1144 if (*appData.cmailGameName != NULLCHAR) {
1146 OpenLoopback(&cmailPR);
1148 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152 DisplayMessage("", "");
1153 if (StrCaseCmp(appData.initialMode, "") == 0) {
1154 initialMode = BeginningOfGame;
1155 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1156 initialMode = TwoMachinesPlay;
1157 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1158 initialMode = AnalyzeFile;
1159 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1160 initialMode = AnalyzeMode;
1161 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1162 initialMode = MachinePlaysWhite;
1163 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1164 initialMode = MachinePlaysBlack;
1165 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1166 initialMode = EditGame;
1167 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1168 initialMode = EditPosition;
1169 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1170 initialMode = Training;
1172 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1173 DisplayFatalError(buf, 0, 2);
1177 if (appData.matchMode) {
1178 /* Set up machine vs. machine match */
1179 if (appData.noChessProgram) {
1180 DisplayFatalError(_("Can't have a match with no chess programs"),
1186 if (*appData.loadGameFile != NULLCHAR) {
1187 int index = appData.loadGameIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadGameFromFile(appData.loadGameFile,
1191 appData.loadGameFile, FALSE)) {
1192 DisplayFatalError(_("Bad game file"), 0, 1);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 int index = appData.loadPositionIndex; // [HGM] autoinc
1197 if(index<0) lastIndex = index = 1;
1198 if (!LoadPositionFromFile(appData.loadPositionFile,
1200 appData.loadPositionFile)) {
1201 DisplayFatalError(_("Bad position file"), 0, 1);
1206 } else if (*appData.cmailGameName != NULLCHAR) {
1207 /* Set up cmail mode */
1208 ReloadCmailMsgEvent(TRUE);
1210 /* Set up other modes */
1211 if (initialMode == AnalyzeFile) {
1212 if (*appData.loadGameFile == NULLCHAR) {
1213 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217 if (*appData.loadGameFile != NULLCHAR) {
1218 (void) LoadGameFromFile(appData.loadGameFile,
1219 appData.loadGameIndex,
1220 appData.loadGameFile, TRUE);
1221 } else if (*appData.loadPositionFile != NULLCHAR) {
1222 (void) LoadPositionFromFile(appData.loadPositionFile,
1223 appData.loadPositionIndex,
1224 appData.loadPositionFile);
1225 /* [HGM] try to make self-starting even after FEN load */
1226 /* to allow automatic setup of fairy variants with wtm */
1227 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1228 gameMode = BeginningOfGame;
1229 setboardSpoiledMachineBlack = 1;
1231 /* [HGM] loadPos: make that every new game uses the setup */
1232 /* from file as long as we do not switch variant */
1233 if(!blackPlaysFirst) { int i;
1234 startedFromPositionFile = TRUE;
1235 CopyBoard(filePosition, boards[0]);
1236 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1239 if (initialMode == AnalyzeMode) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244 if (appData.icsActive) {
1245 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1249 } else if (initialMode == AnalyzeFile) {
1250 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1251 ShowThinkingEvent();
1253 AnalysisPeriodicEvent(1);
1254 } else if (initialMode == MachinePlaysWhite) {
1255 if (appData.noChessProgram) {
1256 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1260 if (appData.icsActive) {
1261 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1265 MachineWhiteEvent();
1266 } else if (initialMode == MachinePlaysBlack) {
1267 if (appData.noChessProgram) {
1268 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1272 if (appData.icsActive) {
1273 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1277 MachineBlackEvent();
1278 } else if (initialMode == TwoMachinesPlay) {
1279 if (appData.noChessProgram) {
1280 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1284 if (appData.icsActive) {
1285 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1290 } else if (initialMode == EditGame) {
1292 } else if (initialMode == EditPosition) {
1293 EditPositionEvent();
1294 } else if (initialMode == Training) {
1295 if (*appData.loadGameFile == NULLCHAR) {
1296 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1305 * Establish will establish a contact to a remote host.port.
1306 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1307 * used to talk to the host.
1308 * Returns 0 if okay, error code if not.
1315 if (*appData.icsCommPort != NULLCHAR) {
1316 /* Talk to the host through a serial comm port */
1317 return OpenCommPort(appData.icsCommPort, &icsPR);
1319 } else if (*appData.gateway != NULLCHAR) {
1320 if (*appData.remoteShell == NULLCHAR) {
1321 /* Use the rcmd protocol to run telnet program on a gateway host */
1322 snprintf(buf, sizeof(buf), "%s %s %s",
1323 appData.telnetProgram, appData.icsHost, appData.icsPort);
1324 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327 /* Use the rsh program to run telnet program on a gateway host */
1328 if (*appData.remoteUser == NULLCHAR) {
1329 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1330 appData.gateway, appData.telnetProgram,
1331 appData.icsHost, appData.icsPort);
1333 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1334 appData.remoteShell, appData.gateway,
1335 appData.remoteUser, appData.telnetProgram,
1336 appData.icsHost, appData.icsPort);
1338 return StartChildProcess(buf, "", &icsPR);
1341 } else if (appData.useTelnet) {
1342 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345 /* TCP socket interface differs somewhat between
1346 Unix and NT; handle details in the front end.
1348 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1353 show_bytes(fp, buf, count)
1359 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1360 fprintf(fp, "\\%03o", *buf & 0xff);
1369 /* Returns an errno value */
1371 OutputMaybeTelnet(pr, message, count, outError)
1377 char buf[8192], *p, *q, *buflim;
1378 int left, newcount, outcount;
1380 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1381 *appData.gateway != NULLCHAR) {
1382 if (appData.debugMode) {
1383 fprintf(debugFP, ">ICS: ");
1384 show_bytes(debugFP, message, count);
1385 fprintf(debugFP, "\n");
1387 return OutputToProcess(pr, message, count, outError);
1390 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1397 if (appData.debugMode) {
1398 fprintf(debugFP, ">ICS: ");
1399 show_bytes(debugFP, buf, newcount);
1400 fprintf(debugFP, "\n");
1402 outcount = OutputToProcess(pr, buf, newcount, outError);
1403 if (outcount < newcount) return -1; /* to be sure */
1410 } else if (((unsigned char) *p) == TN_IAC) {
1411 *q++ = (char) TN_IAC;
1418 if (appData.debugMode) {
1419 fprintf(debugFP, ">ICS: ");
1420 show_bytes(debugFP, buf, newcount);
1421 fprintf(debugFP, "\n");
1423 outcount = OutputToProcess(pr, buf, newcount, outError);
1424 if (outcount < newcount) return -1; /* to be sure */
1429 read_from_player(isr, closure, message, count, error)
1436 int outError, outCount;
1437 static int gotEof = 0;
1439 /* Pass data read from player on to ICS */
1442 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1443 if (outCount < count) {
1444 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446 } else if (count < 0) {
1447 RemoveInputSource(isr);
1448 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1449 } else if (gotEof++ > 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1459 int count, outCount, outError;
1461 if (icsPR == NULL) return;
1464 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1465 if (outCount < count) {
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1470 /* This is used for sending logon scripts to the ICS. Sending
1471 without a delay causes problems when using timestamp on ICC
1472 (at least on my machine). */
1474 SendToICSDelayed(s,msdelay)
1478 int count, outCount, outError;
1480 if (icsPR == NULL) return;
1483 if (appData.debugMode) {
1484 fprintf(debugFP, ">ICS: ");
1485 show_bytes(debugFP, s, count);
1486 fprintf(debugFP, "\n");
1488 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1490 if (outCount < count) {
1491 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1496 /* Remove all highlighting escape sequences in s
1497 Also deletes any suffix starting with '('
1500 StripHighlightAndTitle(s)
1503 static char retbuf[MSG_SIZ];
1506 while (*s != NULLCHAR) {
1507 while (*s == '\033') {
1508 while (*s != NULLCHAR && !isalpha(*s)) s++;
1509 if (*s != NULLCHAR) s++;
1511 while (*s != NULLCHAR && *s != '\033') {
1512 if (*s == '(' || *s == '[') {
1523 /* Remove all highlighting escape sequences in s */
1528 static char retbuf[MSG_SIZ];
1531 while (*s != NULLCHAR) {
1532 while (*s == '\033') {
1533 while (*s != NULLCHAR && !isalpha(*s)) s++;
1534 if (*s != NULLCHAR) s++;
1536 while (*s != NULLCHAR && *s != '\033') {
1544 char *variantNames[] = VARIANT_NAMES;
1549 return variantNames[v];
1553 /* Identify a variant from the strings the chess servers use or the
1554 PGN Variant tag names we use. */
1561 VariantClass v = VariantNormal;
1562 int i, found = FALSE;
1567 /* [HGM] skip over optional board-size prefixes */
1568 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1569 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1570 while( *e++ != '_');
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574 if (StrCaseStr(e, variantNames[i])) {
1575 v = (VariantClass) i;
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583 || StrCaseStr(e, "wild/fr")
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585 v = VariantFischeRandom;
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587 (i = 1, p = StrCaseStr(e, "w"))) {
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1596 case 0: /* FICS only, actually */
1598 /* Castling legal even if K starts on d-file */
1599 v = VariantWildCastle;
1604 /* Castling illegal even if K & R happen to start in
1605 normal positions. */
1606 v = VariantNoCastle;
1619 /* Castling legal iff K & R start in normal positions */
1625 /* Special wilds for position setup; unclear what to do here */
1626 v = VariantLoadable;
1629 /* Bizarre ICC game */
1630 v = VariantTwoKings;
1633 v = VariantKriegspiel;
1639 v = VariantFischeRandom;
1642 v = VariantCrazyhouse;
1645 v = VariantBughouse;
1651 /* Not quite the same as FICS suicide! */
1652 v = VariantGiveaway;
1658 v = VariantShatranj;
1661 /* Temporary names for future ICC types. The name *will* change in
1662 the next xboard/WinBoard release after ICC defines it. */
1700 v = VariantCapablanca;
1703 v = VariantKnightmate;
1709 v = VariantCylinder;
1715 v = VariantCapaRandom;
1718 v = VariantBerolina;
1730 /* Found "wild" or "w" in the string but no number;
1731 must assume it's normal chess. */
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
1736 DisplayError(buf, 0);
1742 if (appData.debugMode) {
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744 e, wnum, VariantName(v));
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753 advance *index beyond it, and set leftover_start to the new value of
1754 *index; else return FALSE. If pattern contains the character '*', it
1755 matches any sequence of characters not containing '\r', '\n', or the
1756 character following the '*' (if any), and the matched sequence(s) are
1757 copied into star_match.
1760 looking_at(buf, index, pattern)
1765 char *bufp = &buf[*index], *patternp = pattern;
1767 char *matchp = star_match[0];
1770 if (*patternp == NULLCHAR) {
1771 *index = leftover_start = bufp - buf;
1775 if (*bufp == NULLCHAR) return FALSE;
1776 if (*patternp == '*') {
1777 if (*bufp == *(patternp + 1)) {
1779 matchp = star_match[++star_count];
1783 } else if (*bufp == '\n' || *bufp == '\r') {
1785 if (*patternp == NULLCHAR)
1790 *matchp++ = *bufp++;
1794 if (*patternp != *bufp) return FALSE;
1801 SendToPlayer(data, length)
1805 int error, outCount;
1806 outCount = OutputToProcess(NoProc, data, length, &error);
1807 if (outCount < length) {
1808 DisplayFatalError(_("Error writing to display"), error, 1);
1813 PackHolding(packed, holding)
1825 switch (runlength) {
1836 sprintf(q, "%d", runlength);
1848 /* Telnet protocol requests from the front end */
1850 TelnetRequest(ddww, option)
1851 unsigned char ddww, option;
1853 unsigned char msg[3];
1854 int outCount, outError;
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858 if (appData.debugMode) {
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1875 sprintf(buf1, "%d", ddww);
1884 sprintf(buf2, "%d", option);
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 if (!appData.icsActive) return;
1902 TelnetRequest(TN_DO, TN_ECHO);
1908 if (!appData.icsActive) return;
1909 TelnetRequest(TN_DONT, TN_ECHO);
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 /* put the holdings sent to us by the server on the board holdings area */
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1920 if(gameInfo.holdingsWidth < 2) return;
1922 if( (int)lowestPiece >= BlackPawn ) {
1925 holdingsStartRow = BOARD_HEIGHT-1;
1928 holdingsColumn = BOARD_WIDTH-1;
1929 countsColumn = BOARD_WIDTH-2;
1930 holdingsStartRow = 0;
1934 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1935 board[i][holdingsColumn] = EmptySquare;
1936 board[i][countsColumn] = (ChessSquare) 0;
1938 while( (p=*holdings++) != NULLCHAR ) {
1939 piece = CharToPiece( ToUpper(p) );
1940 if(piece == EmptySquare) continue;
1941 /*j = (int) piece - (int) WhitePawn;*/
1942 j = PieceToNumber(piece);
1943 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1944 if(j < 0) continue; /* should not happen */
1945 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1946 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1947 board[holdingsStartRow+j*direction][countsColumn]++;
1954 VariantSwitch(Board board, VariantClass newVariant)
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1958 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1960 startedFromPositionFile = FALSE;
1961 if(gameInfo.variant == newVariant) return;
1963 /* [HGM] This routine is called each time an assignment is made to
1964 * gameInfo.variant during a game, to make sure the board sizes
1965 * are set to match the new variant. If that means adding or deleting
1966 * holdings, we shift the playing board accordingly
1967 * This kludge is needed because in ICS observe mode, we get boards
1968 * of an ongoing game without knowing the variant, and learn about the
1969 * latter only later. This can be because of the move list we requested,
1970 * in which case the game history is refilled from the beginning anyway,
1971 * but also when receiving holdings of a crazyhouse game. In the latter
1972 * case we want to add those holdings to the already received position.
1976 if (appData.debugMode) {
1977 fprintf(debugFP, "Switch board from %s to %s\n",
1978 VariantName(gameInfo.variant), VariantName(newVariant));
1979 setbuf(debugFP, NULL);
1981 shuffleOpenings = 0; /* [HGM] shuffle */
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983 switch(newVariant) {
1985 newWidth = 9; newHeight = 9;
1986 gameInfo.holdingsSize = 7;
1987 case VariantBughouse:
1988 case VariantCrazyhouse:
1989 newHoldingsWidth = 2; break;
1991 newHoldingsWidth = gameInfo.holdingsSize = 0;
1994 if(newWidth != gameInfo.boardWidth ||
1995 newHeight != gameInfo.boardHeight ||
1996 newHoldingsWidth != gameInfo.holdingsWidth ) {
1998 /* shift position to new playing area, if needed */
1999 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2000 for(i=0; i<BOARD_HEIGHT; i++)
2001 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2002 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2004 for(i=0; i<newHeight; i++) {
2005 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2006 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2008 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2009 for(i=0; i<BOARD_HEIGHT; i++)
2010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015 gameInfo.boardWidth = newWidth;
2016 gameInfo.boardHeight = newHeight;
2017 gameInfo.holdingsWidth = newHoldingsWidth;
2018 gameInfo.variant = newVariant;
2019 InitDrawingSizes(-2, 0);
2021 /* [HGM] The following should definitely be solved in a better way */
2023 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2024 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2025 saveEP = epStatus[0];
2027 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2029 epStatus[0] = saveEP;
2030 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2031 CopyBoard(tempBoard, board); /* restore position received from ICS */
2033 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2035 forwardMostMove = oldForwardMostMove;
2036 backwardMostMove = oldBackwardMostMove;
2037 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2040 static int loggedOn = FALSE;
2042 /*-- Game start info cache: --*/
2044 char gs_kind[MSG_SIZ];
2045 static char player1Name[128] = "";
2046 static char player2Name[128] = "";
2047 static int player1Rating = -1;
2048 static int player2Rating = -1;
2049 /*----------------------------*/
2051 ColorClass curColor = ColorNormal;
2052 int suppressKibitz = 0;
2055 read_from_ics(isr, closure, data, count, error)
2062 #define BUF_SIZE 8192
2063 #define STARTED_NONE 0
2064 #define STARTED_MOVES 1
2065 #define STARTED_BOARD 2
2066 #define STARTED_OBSERVE 3
2067 #define STARTED_HOLDINGS 4
2068 #define STARTED_CHATTER 5
2069 #define STARTED_COMMENT 6
2070 #define STARTED_MOVES_NOHIDE 7
2072 static int started = STARTED_NONE;
2073 static char parse[20000];
2074 static int parse_pos = 0;
2075 static char buf[BUF_SIZE + 1];
2076 static int firstTime = TRUE, intfSet = FALSE;
2077 static ColorClass prevColor = ColorNormal;
2078 static int savingComment = FALSE;
2084 int backup; /* [DM] For zippy color lines */
2087 if (appData.debugMode) {
2089 fprintf(debugFP, "<ICS: ");
2090 show_bytes(debugFP, data, count);
2091 fprintf(debugFP, "\n");
2095 if (appData.debugMode) { int f = forwardMostMove;
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2100 /* If last read ended with a partial line that we couldn't parse,
2101 prepend it to the new read and try again. */
2102 if (leftover_len > 0) {
2103 for (i=0; i<leftover_len; i++)
2104 buf[i] = buf[leftover_start + i];
2107 /* Copy in new characters, removing nulls and \r's */
2108 buf_len = leftover_len;
2109 for (i = 0; i < count; i++) {
2110 if (data[i] != NULLCHAR && data[i] != '\r')
2111 buf[buf_len++] = data[i];
2112 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2113 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2114 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2115 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2119 buf[buf_len] = NULLCHAR;
2120 next_out = leftover_len;
2124 while (i < buf_len) {
2125 /* Deal with part of the TELNET option negotiation
2126 protocol. We refuse to do anything beyond the
2127 defaults, except that we allow the WILL ECHO option,
2128 which ICS uses to turn off password echoing when we are
2129 directly connected to it. We reject this option
2130 if localLineEditing mode is on (always on in xboard)
2131 and we are talking to port 23, which might be a real
2132 telnet server that will try to keep WILL ECHO on permanently.
2134 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2135 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2136 unsigned char option;
2138 switch ((unsigned char) buf[++i]) {
2140 if (appData.debugMode)
2141 fprintf(debugFP, "\n<WILL ");
2142 switch (option = (unsigned char) buf[++i]) {
2144 if (appData.debugMode)
2145 fprintf(debugFP, "ECHO ");
2146 /* Reply only if this is a change, according
2147 to the protocol rules. */
2148 if (remoteEchoOption) break;
2149 if (appData.localLineEditing &&
2150 atoi(appData.icsPort) == TN_PORT) {
2151 TelnetRequest(TN_DONT, TN_ECHO);
2154 TelnetRequest(TN_DO, TN_ECHO);
2155 remoteEchoOption = TRUE;
2159 if (appData.debugMode)
2160 fprintf(debugFP, "%d ", option);
2161 /* Whatever this is, we don't want it. */
2162 TelnetRequest(TN_DONT, option);
2167 if (appData.debugMode)
2168 fprintf(debugFP, "\n<WONT ");
2169 switch (option = (unsigned char) buf[++i]) {
2171 if (appData.debugMode)
2172 fprintf(debugFP, "ECHO ");
2173 /* Reply only if this is a change, according
2174 to the protocol rules. */
2175 if (!remoteEchoOption) break;
2177 TelnetRequest(TN_DONT, TN_ECHO);
2178 remoteEchoOption = FALSE;
2181 if (appData.debugMode)
2182 fprintf(debugFP, "%d ", (unsigned char) option);
2183 /* Whatever this is, it must already be turned
2184 off, because we never agree to turn on
2185 anything non-default, so according to the
2186 protocol rules, we don't reply. */
2191 if (appData.debugMode)
2192 fprintf(debugFP, "\n<DO ");
2193 switch (option = (unsigned char) buf[++i]) {
2195 /* Whatever this is, we refuse to do it. */
2196 if (appData.debugMode)
2197 fprintf(debugFP, "%d ", option);
2198 TelnetRequest(TN_WONT, option);
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<DONT ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 if (appData.debugMode)
2208 fprintf(debugFP, "%d ", option);
2209 /* Whatever this is, we are already not doing
2210 it, because we never agree to do anything
2211 non-default, so according to the protocol
2212 rules, we don't reply. */
2217 if (appData.debugMode)
2218 fprintf(debugFP, "\n<IAC ");
2219 /* Doubled IAC; pass it through */
2223 if (appData.debugMode)
2224 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2225 /* Drop all other telnet commands on the floor */
2228 if (oldi > next_out)
2229 SendToPlayer(&buf[next_out], oldi - next_out);
2235 /* OK, this at least will *usually* work */
2236 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2240 if (loggedOn && !intfSet) {
2241 if (ics_type == ICS_ICC) {
2243 "/set-quietly interface %s\n/set-quietly style 12\n",
2246 } else if (ics_type == ICS_CHESSNET) {
2247 sprintf(str, "/style 12\n");
2249 strcpy(str, "alias $ @\n$set interface ");
2250 strcat(str, programVersion);
2251 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2253 strcat(str, "$iset nohighlight 1\n");
2255 strcat(str, "$iset lock 1\n$style 12\n");
2261 if (started == STARTED_COMMENT) {
2262 /* Accumulate characters in comment */
2263 parse[parse_pos++] = buf[i];
2264 if (buf[i] == '\n') {
2265 parse[parse_pos] = NULLCHAR;
2266 if(!suppressKibitz) // [HGM] kibitz
2267 AppendComment(forwardMostMove, StripHighlight(parse));
2268 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2269 int nrDigit = 0, nrAlph = 0, i;
2270 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2271 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2272 parse[parse_pos] = NULLCHAR;
2273 // try to be smart: if it does not look like search info, it should go to
2274 // ICS interaction window after all, not to engine-output window.
2275 for(i=0; i<parse_pos; i++) { // count letters and digits
2276 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2277 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2278 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2280 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2281 int depth=0; float score;
2282 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2283 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2284 pvInfoList[forwardMostMove-1].depth = depth;
2285 pvInfoList[forwardMostMove-1].score = 100*score;
2287 OutputKibitz(suppressKibitz, parse);
2290 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2291 SendToPlayer(tmp, strlen(tmp));
2294 started = STARTED_NONE;
2296 /* Don't match patterns against characters in chatter */
2301 if (started == STARTED_CHATTER) {
2302 if (buf[i] != '\n') {
2303 /* Don't match patterns against characters in chatter */
2307 started = STARTED_NONE;
2310 /* Kludge to deal with rcmd protocol */
2311 if (firstTime && looking_at(buf, &i, "\001*")) {
2312 DisplayFatalError(&buf[1], 0, 1);
2318 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2321 if (appData.debugMode)
2322 fprintf(debugFP, "ics_type %d\n", ics_type);
2325 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2326 ics_type = ICS_FICS;
2328 if (appData.debugMode)
2329 fprintf(debugFP, "ics_type %d\n", ics_type);
2332 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2333 ics_type = ICS_CHESSNET;
2335 if (appData.debugMode)
2336 fprintf(debugFP, "ics_type %d\n", ics_type);
2341 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2342 looking_at(buf, &i, "Logging you in as \"*\"") ||
2343 looking_at(buf, &i, "will be \"*\""))) {
2344 strcpy(ics_handle, star_match[0]);
2348 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2350 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2351 DisplayIcsInteractionTitle(buf);
2352 have_set_title = TRUE;
2355 /* skip finger notes */
2356 if (started == STARTED_NONE &&
2357 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2358 (buf[i] == '1' && buf[i+1] == '0')) &&
2359 buf[i+2] == ':' && buf[i+3] == ' ') {
2360 started = STARTED_CHATTER;
2365 /* skip formula vars */
2366 if (started == STARTED_NONE &&
2367 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2368 started = STARTED_CHATTER;
2374 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2375 if (appData.autoKibitz && started == STARTED_NONE &&
2376 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2377 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2378 if(looking_at(buf, &i, "* kibitzes: ") &&
2379 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2380 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2381 suppressKibitz = TRUE;
2382 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2383 && (gameMode == IcsPlayingWhite)) ||
2384 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2385 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2386 started = STARTED_CHATTER; // own kibitz we simply discard
2388 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2389 parse_pos = 0; parse[0] = NULLCHAR;
2390 savingComment = TRUE;
2391 suppressKibitz = gameMode != IcsObserving ? 2 :
2392 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2396 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2397 started = STARTED_CHATTER;
2398 suppressKibitz = TRUE;
2400 } // [HGM] kibitz: end of patch
2402 if (appData.zippyTalk || appData.zippyPlay) {
2403 /* [DM] Backup address for color zippy lines */
2407 if (loggedOn == TRUE)
2408 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2409 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2411 if (ZippyControl(buf, &i) ||
2412 ZippyConverse(buf, &i) ||
2413 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2415 if (!appData.colorize) continue;
2419 } // [DM] 'else { ' deleted
2420 if (/* Don't color "message" or "messages" output */
2421 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2422 looking_at(buf, &i, "*. * at *:*: ") ||
2423 looking_at(buf, &i, "--* (*:*): ") ||
2424 /* Regular tells and says */
2425 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2426 looking_at(buf, &i, "* (your partner) tells you: ") ||
2427 looking_at(buf, &i, "* says: ") ||
2428 /* Message notifications (same color as tells) */
2429 looking_at(buf, &i, "* has left a message ") ||
2430 looking_at(buf, &i, "* just sent you a message:\n") ||
2431 /* Whispers and kibitzes */
2432 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2433 looking_at(buf, &i, "* kibitzes: ") ||
2435 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2437 if (tkind == 1 && strchr(star_match[0], ':')) {
2438 /* Avoid "tells you:" spoofs in channels */
2441 if (star_match[0][0] == NULLCHAR ||
2442 strchr(star_match[0], ' ') ||
2443 (tkind == 3 && strchr(star_match[1], ' '))) {
2444 /* Reject bogus matches */
2447 if (appData.colorize) {
2448 if (oldi > next_out) {
2449 SendToPlayer(&buf[next_out], oldi - next_out);
2454 Colorize(ColorTell, FALSE);
2455 curColor = ColorTell;
2458 Colorize(ColorKibitz, FALSE);
2459 curColor = ColorKibitz;
2462 p = strrchr(star_match[1], '(');
2469 Colorize(ColorChannel1, FALSE);
2470 curColor = ColorChannel1;
2472 Colorize(ColorChannel, FALSE);
2473 curColor = ColorChannel;
2477 curColor = ColorNormal;
2481 if (started == STARTED_NONE && appData.autoComment &&
2482 (gameMode == IcsObserving ||
2483 gameMode == IcsPlayingWhite ||
2484 gameMode == IcsPlayingBlack)) {
2485 parse_pos = i - oldi;
2486 memcpy(parse, &buf[oldi], parse_pos);
2487 parse[parse_pos] = NULLCHAR;
2488 started = STARTED_COMMENT;
2489 savingComment = TRUE;
2491 started = STARTED_CHATTER;
2492 savingComment = FALSE;
2499 if (looking_at(buf, &i, "* s-shouts: ") ||
2500 looking_at(buf, &i, "* c-shouts: ")) {
2501 if (appData.colorize) {
2502 if (oldi > next_out) {
2503 SendToPlayer(&buf[next_out], oldi - next_out);
2506 Colorize(ColorSShout, FALSE);
2507 curColor = ColorSShout;
2510 started = STARTED_CHATTER;
2514 if (looking_at(buf, &i, "--->")) {
2519 if (looking_at(buf, &i, "* shouts: ") ||
2520 looking_at(buf, &i, "--> ")) {
2521 if (appData.colorize) {
2522 if (oldi > next_out) {
2523 SendToPlayer(&buf[next_out], oldi - next_out);
2526 Colorize(ColorShout, FALSE);
2527 curColor = ColorShout;
2530 started = STARTED_CHATTER;
2534 if (looking_at( buf, &i, "Challenge:")) {
2535 if (appData.colorize) {
2536 if (oldi > next_out) {
2537 SendToPlayer(&buf[next_out], oldi - next_out);
2540 Colorize(ColorChallenge, FALSE);
2541 curColor = ColorChallenge;
2547 if (looking_at(buf, &i, "* offers you") ||
2548 looking_at(buf, &i, "* offers to be") ||
2549 looking_at(buf, &i, "* would like to") ||
2550 looking_at(buf, &i, "* requests to") ||
2551 looking_at(buf, &i, "Your opponent offers") ||
2552 looking_at(buf, &i, "Your opponent requests")) {
2554 if (appData.colorize) {
2555 if (oldi > next_out) {
2556 SendToPlayer(&buf[next_out], oldi - next_out);
2559 Colorize(ColorRequest, FALSE);
2560 curColor = ColorRequest;
2565 if (looking_at(buf, &i, "* (*) seeking")) {
2566 if (appData.colorize) {
2567 if (oldi > next_out) {
2568 SendToPlayer(&buf[next_out], oldi - next_out);
2571 Colorize(ColorSeek, FALSE);
2572 curColor = ColorSeek;
2577 if (looking_at(buf, &i, "\\ ")) {
2578 if (prevColor != ColorNormal) {
2579 if (oldi > next_out) {
2580 SendToPlayer(&buf[next_out], oldi - next_out);
2583 Colorize(prevColor, TRUE);
2584 curColor = prevColor;
2586 if (savingComment) {
2587 parse_pos = i - oldi;
2588 memcpy(parse, &buf[oldi], parse_pos);
2589 parse[parse_pos] = NULLCHAR;
2590 started = STARTED_COMMENT;
2592 started = STARTED_CHATTER;
2597 if (looking_at(buf, &i, "Black Strength :") ||
2598 looking_at(buf, &i, "<<< style 10 board >>>") ||
2599 looking_at(buf, &i, "<10>") ||
2600 looking_at(buf, &i, "#@#")) {
2601 /* Wrong board style */
2603 SendToICS(ics_prefix);
2604 SendToICS("set style 12\n");
2605 SendToICS(ics_prefix);
2606 SendToICS("refresh\n");
2610 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2612 have_sent_ICS_logon = 1;
2616 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2617 (looking_at(buf, &i, "\n<12> ") ||
2618 looking_at(buf, &i, "<12> "))) {
2620 if (oldi > next_out) {
2621 SendToPlayer(&buf[next_out], oldi - next_out);
2624 started = STARTED_BOARD;
2629 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2630 looking_at(buf, &i, "<b1> ")) {
2631 if (oldi > next_out) {
2632 SendToPlayer(&buf[next_out], oldi - next_out);
2635 started = STARTED_HOLDINGS;
2640 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2642 /* Header for a move list -- first line */
2644 switch (ics_getting_history) {
2648 case BeginningOfGame:
2649 /* User typed "moves" or "oldmoves" while we
2650 were idle. Pretend we asked for these
2651 moves and soak them up so user can step
2652 through them and/or save them.
2655 gameMode = IcsObserving;
2658 ics_getting_history = H_GOT_UNREQ_HEADER;
2660 case EditGame: /*?*/
2661 case EditPosition: /*?*/
2662 /* Should above feature work in these modes too? */
2663 /* For now it doesn't */
2664 ics_getting_history = H_GOT_UNWANTED_HEADER;
2667 ics_getting_history = H_GOT_UNWANTED_HEADER;
2672 /* Is this the right one? */
2673 if (gameInfo.white && gameInfo.black &&
2674 strcmp(gameInfo.white, star_match[0]) == 0 &&
2675 strcmp(gameInfo.black, star_match[2]) == 0) {
2677 ics_getting_history = H_GOT_REQ_HEADER;
2680 case H_GOT_REQ_HEADER:
2681 case H_GOT_UNREQ_HEADER:
2682 case H_GOT_UNWANTED_HEADER:
2683 case H_GETTING_MOVES:
2684 /* Should not happen */
2685 DisplayError(_("Error gathering move list: two headers"), 0);
2686 ics_getting_history = H_FALSE;
2690 /* Save player ratings into gameInfo if needed */
2691 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2692 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2693 (gameInfo.whiteRating == -1 ||
2694 gameInfo.blackRating == -1)) {
2696 gameInfo.whiteRating = string_to_rating(star_match[1]);
2697 gameInfo.blackRating = string_to_rating(star_match[3]);
2698 if (appData.debugMode)
2699 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2700 gameInfo.whiteRating, gameInfo.blackRating);
2705 if (looking_at(buf, &i,
2706 "* * match, initial time: * minute*, increment: * second")) {
2707 /* Header for a move list -- second line */
2708 /* Initial board will follow if this is a wild game */
2709 if (gameInfo.event != NULL) free(gameInfo.event);
2710 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2711 gameInfo.event = StrSave(str);
2712 /* [HGM] we switched variant. Translate boards if needed. */
2713 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2717 if (looking_at(buf, &i, "Move ")) {
2718 /* Beginning of a move list */
2719 switch (ics_getting_history) {
2721 /* Normally should not happen */
2722 /* Maybe user hit reset while we were parsing */
2725 /* Happens if we are ignoring a move list that is not
2726 * the one we just requested. Common if the user
2727 * tries to observe two games without turning off
2730 case H_GETTING_MOVES:
2731 /* Should not happen */
2732 DisplayError(_("Error gathering move list: nested"), 0);
2733 ics_getting_history = H_FALSE;
2735 case H_GOT_REQ_HEADER:
2736 ics_getting_history = H_GETTING_MOVES;
2737 started = STARTED_MOVES;
2739 if (oldi > next_out) {
2740 SendToPlayer(&buf[next_out], oldi - next_out);
2743 case H_GOT_UNREQ_HEADER:
2744 ics_getting_history = H_GETTING_MOVES;
2745 started = STARTED_MOVES_NOHIDE;
2748 case H_GOT_UNWANTED_HEADER:
2749 ics_getting_history = H_FALSE;
2755 if (looking_at(buf, &i, "% ") ||
2756 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2757 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2758 savingComment = FALSE;
2761 case STARTED_MOVES_NOHIDE:
2762 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2763 parse[parse_pos + i - oldi] = NULLCHAR;
2764 ParseGameHistory(parse);
2766 if (appData.zippyPlay && first.initDone) {
2767 FeedMovesToProgram(&first, forwardMostMove);
2768 if (gameMode == IcsPlayingWhite) {
2769 if (WhiteOnMove(forwardMostMove)) {
2770 if (first.sendTime) {
2771 if (first.useColors) {
2772 SendToProgram("black\n", &first);
2774 SendTimeRemaining(&first, TRUE);
2777 if (first.useColors) {
2778 SendToProgram("white\ngo\n", &first);
2780 SendToProgram("go\n", &first);
2783 if (first.useColors) {
2784 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2786 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2788 first.maybeThinking = TRUE;
2790 if (first.usePlayother) {
2791 if (first.sendTime) {
2792 SendTimeRemaining(&first, TRUE);
2794 SendToProgram("playother\n", &first);
2800 } else if (gameMode == IcsPlayingBlack) {
2801 if (!WhiteOnMove(forwardMostMove)) {
2802 if (first.sendTime) {
2803 if (first.useColors) {
2804 SendToProgram("white\n", &first);
2806 SendTimeRemaining(&first, FALSE);
2809 if (first.useColors) {
2810 SendToProgram("black\ngo\n", &first);
2812 SendToProgram("go\n", &first);
2815 if (first.useColors) {
2816 SendToProgram("black\n", &first);
2818 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2820 first.maybeThinking = TRUE;
2822 if (first.usePlayother) {
2823 if (first.sendTime) {
2824 SendTimeRemaining(&first, FALSE);
2826 SendToProgram("playother\n", &first);
2835 if (gameMode == IcsObserving && ics_gamenum == -1) {
2836 /* Moves came from oldmoves or moves command
2837 while we weren't doing anything else.
2839 currentMove = forwardMostMove;
2840 ClearHighlights();/*!!could figure this out*/
2841 flipView = appData.flipView;
2842 DrawPosition(FALSE, boards[currentMove]);
2843 DisplayBothClocks();
2844 sprintf(str, "%s vs. %s",
2845 gameInfo.white, gameInfo.black);
2849 /* Moves were history of an active game */
2850 if (gameInfo.resultDetails != NULL) {
2851 free(gameInfo.resultDetails);
2852 gameInfo.resultDetails = NULL;
2855 HistorySet(parseList, backwardMostMove,
2856 forwardMostMove, currentMove-1);
2857 DisplayMove(currentMove - 1);
2858 if (started == STARTED_MOVES) next_out = i;
2859 started = STARTED_NONE;
2860 ics_getting_history = H_FALSE;
2863 case STARTED_OBSERVE:
2864 started = STARTED_NONE;
2865 SendToICS(ics_prefix);
2866 SendToICS("refresh\n");
2872 if(bookHit) { // [HGM] book: simulate book reply
2873 static char bookMove[MSG_SIZ]; // a bit generous?
2875 programStats.nodes = programStats.depth = programStats.time =
2876 programStats.score = programStats.got_only_move = 0;
2877 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2879 strcpy(bookMove, "move ");
2880 strcat(bookMove, bookHit);
2881 HandleMachineMove(bookMove, &first);
2886 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2887 started == STARTED_HOLDINGS ||
2888 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2889 /* Accumulate characters in move list or board */
2890 parse[parse_pos++] = buf[i];
2893 /* Start of game messages. Mostly we detect start of game
2894 when the first board image arrives. On some versions
2895 of the ICS, though, we need to do a "refresh" after starting
2896 to observe in order to get the current board right away. */
2897 if (looking_at(buf, &i, "Adding game * to observation list")) {
2898 started = STARTED_OBSERVE;
2902 /* Handle auto-observe */
2903 if (appData.autoObserve &&
2904 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2905 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2907 /* Choose the player that was highlighted, if any. */
2908 if (star_match[0][0] == '\033' ||
2909 star_match[1][0] != '\033') {
2910 player = star_match[0];
2912 player = star_match[2];
2914 sprintf(str, "%sobserve %s\n",
2915 ics_prefix, StripHighlightAndTitle(player));
2918 /* Save ratings from notify string */
2919 strcpy(player1Name, star_match[0]);
2920 player1Rating = string_to_rating(star_match[1]);
2921 strcpy(player2Name, star_match[2]);
2922 player2Rating = string_to_rating(star_match[3]);
2924 if (appData.debugMode)
2926 "Ratings from 'Game notification:' %s %d, %s %d\n",
2927 player1Name, player1Rating,
2928 player2Name, player2Rating);
2933 /* Deal with automatic examine mode after a game,
2934 and with IcsObserving -> IcsExamining transition */
2935 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2936 looking_at(buf, &i, "has made you an examiner of game *")) {
2938 int gamenum = atoi(star_match[0]);
2939 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2940 gamenum == ics_gamenum) {
2941 /* We were already playing or observing this game;
2942 no need to refetch history */
2943 gameMode = IcsExamining;
2945 pauseExamForwardMostMove = forwardMostMove;
2946 } else if (currentMove < forwardMostMove) {
2947 ForwardInner(forwardMostMove);
2950 /* I don't think this case really can happen */
2951 SendToICS(ics_prefix);
2952 SendToICS("refresh\n");
2957 /* Error messages */
2958 // if (ics_user_moved) {
2959 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2960 if (looking_at(buf, &i, "Illegal move") ||
2961 looking_at(buf, &i, "Not a legal move") ||
2962 looking_at(buf, &i, "Your king is in check") ||
2963 looking_at(buf, &i, "It isn't your turn") ||
2964 looking_at(buf, &i, "It is not your move")) {
2966 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2967 currentMove = --forwardMostMove;
2968 DisplayMove(currentMove - 1); /* before DMError */
2969 DrawPosition(FALSE, boards[currentMove]);
2971 DisplayBothClocks();
2973 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2979 if (looking_at(buf, &i, "still have time") ||
2980 looking_at(buf, &i, "not out of time") ||
2981 looking_at(buf, &i, "either player is out of time") ||
2982 looking_at(buf, &i, "has timeseal; checking")) {
2983 /* We must have called his flag a little too soon */
2984 whiteFlag = blackFlag = FALSE;
2988 if (looking_at(buf, &i, "added * seconds to") ||
2989 looking_at(buf, &i, "seconds were added to")) {
2990 /* Update the clocks */
2991 SendToICS(ics_prefix);
2992 SendToICS("refresh\n");
2996 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2997 ics_clock_paused = TRUE;
3002 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3003 ics_clock_paused = FALSE;
3008 /* Grab player ratings from the Creating: message.
3009 Note we have to check for the special case when
3010 the ICS inserts things like [white] or [black]. */
3011 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3012 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3014 0 player 1 name (not necessarily white)
3016 2 empty, white, or black (IGNORED)
3017 3 player 2 name (not necessarily black)
3020 The names/ratings are sorted out when the game
3021 actually starts (below).
3023 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3024 player1Rating = string_to_rating(star_match[1]);
3025 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3026 player2Rating = string_to_rating(star_match[4]);
3028 if (appData.debugMode)
3030 "Ratings from 'Creating:' %s %d, %s %d\n",
3031 player1Name, player1Rating,
3032 player2Name, player2Rating);
3037 /* Improved generic start/end-of-game messages */
3038 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3039 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3040 /* If tkind == 0: */
3041 /* star_match[0] is the game number */
3042 /* [1] is the white player's name */
3043 /* [2] is the black player's name */
3044 /* For end-of-game: */
3045 /* [3] is the reason for the game end */
3046 /* [4] is a PGN end game-token, preceded by " " */
3047 /* For start-of-game: */
3048 /* [3] begins with "Creating" or "Continuing" */
3049 /* [4] is " *" or empty (don't care). */
3050 int gamenum = atoi(star_match[0]);
3051 char *whitename, *blackname, *why, *endtoken;
3052 ChessMove endtype = (ChessMove) 0;
3055 whitename = star_match[1];
3056 blackname = star_match[2];
3057 why = star_match[3];
3058 endtoken = star_match[4];
3060 whitename = star_match[1];
3061 blackname = star_match[3];
3062 why = star_match[5];
3063 endtoken = star_match[6];
3066 /* Game start messages */
3067 if (strncmp(why, "Creating ", 9) == 0 ||
3068 strncmp(why, "Continuing ", 11) == 0) {
3069 gs_gamenum = gamenum;
3070 strcpy(gs_kind, strchr(why, ' ') + 1);
3072 if (appData.zippyPlay) {
3073 ZippyGameStart(whitename, blackname);
3079 /* Game end messages */
3080 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3081 ics_gamenum != gamenum) {
3084 while (endtoken[0] == ' ') endtoken++;
3085 switch (endtoken[0]) {
3088 endtype = GameUnfinished;
3091 endtype = BlackWins;
3094 if (endtoken[1] == '/')
3095 endtype = GameIsDrawn;
3097 endtype = WhiteWins;
3100 GameEnds(endtype, why, GE_ICS);
3102 if (appData.zippyPlay && first.initDone) {
3103 ZippyGameEnd(endtype, why);
3104 if (first.pr == NULL) {
3105 /* Start the next process early so that we'll
3106 be ready for the next challenge */
3107 StartChessProgram(&first);
3109 /* Send "new" early, in case this command takes
3110 a long time to finish, so that we'll be ready
3111 for the next challenge. */
3112 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3119 if (looking_at(buf, &i, "Removing game * from observation") ||
3120 looking_at(buf, &i, "no longer observing game *") ||
3121 looking_at(buf, &i, "Game * (*) has no examiners")) {
3122 if (gameMode == IcsObserving &&
3123 atoi(star_match[0]) == ics_gamenum)
3125 /* icsEngineAnalyze */
3126 if (appData.icsEngineAnalyze) {
3133 ics_user_moved = FALSE;
3138 if (looking_at(buf, &i, "no longer examining game *")) {
3139 if (gameMode == IcsExamining &&
3140 atoi(star_match[0]) == ics_gamenum)
3144 ics_user_moved = FALSE;
3149 /* Advance leftover_start past any newlines we find,
3150 so only partial lines can get reparsed */
3151 if (looking_at(buf, &i, "\n")) {
3152 prevColor = curColor;
3153 if (curColor != ColorNormal) {
3154 if (oldi > next_out) {
3155 SendToPlayer(&buf[next_out], oldi - next_out);
3158 Colorize(ColorNormal, FALSE);
3159 curColor = ColorNormal;
3161 if (started == STARTED_BOARD) {
3162 started = STARTED_NONE;
3163 parse[parse_pos] = NULLCHAR;
3164 ParseBoard12(parse);
3167 /* Send premove here */
3168 if (appData.premove) {
3170 if (currentMove == 0 &&
3171 gameMode == IcsPlayingWhite &&
3172 appData.premoveWhite) {
3173 sprintf(str, "%s%s\n", ics_prefix,
3174 appData.premoveWhiteText);
3175 if (appData.debugMode)
3176 fprintf(debugFP, "Sending premove:\n");
3178 } else if (currentMove == 1 &&
3179 gameMode == IcsPlayingBlack &&
3180 appData.premoveBlack) {
3181 sprintf(str, "%s%s\n", ics_prefix,
3182 appData.premoveBlackText);
3183 if (appData.debugMode)
3184 fprintf(debugFP, "Sending premove:\n");
3186 } else if (gotPremove) {
3188 ClearPremoveHighlights();
3189 if (appData.debugMode)
3190 fprintf(debugFP, "Sending premove:\n");
3191 UserMoveEvent(premoveFromX, premoveFromY,
3192 premoveToX, premoveToY,
3197 /* Usually suppress following prompt */
3198 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3199 if (looking_at(buf, &i, "*% ")) {
3200 savingComment = FALSE;
3204 } else if (started == STARTED_HOLDINGS) {
3206 char new_piece[MSG_SIZ];
3207 started = STARTED_NONE;
3208 parse[parse_pos] = NULLCHAR;
3209 if (appData.debugMode)
3210 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3211 parse, currentMove);
3212 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3213 gamenum == ics_gamenum) {
3214 if (gameInfo.variant == VariantNormal) {
3215 /* [HGM] We seem to switch variant during a game!
3216 * Presumably no holdings were displayed, so we have
3217 * to move the position two files to the right to
3218 * create room for them!
3220 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3221 /* Get a move list just to see the header, which
3222 will tell us whether this is really bug or zh */
3223 if (ics_getting_history == H_FALSE) {
3224 ics_getting_history = H_REQUESTED;
3225 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3229 new_piece[0] = NULLCHAR;
3230 sscanf(parse, "game %d white [%s black [%s <- %s",
3231 &gamenum, white_holding, black_holding,
3233 white_holding[strlen(white_holding)-1] = NULLCHAR;
3234 black_holding[strlen(black_holding)-1] = NULLCHAR;
3235 /* [HGM] copy holdings to board holdings area */
3236 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3237 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3239 if (appData.zippyPlay && first.initDone) {
3240 ZippyHoldings(white_holding, black_holding,
3244 if (tinyLayout || smallLayout) {
3245 char wh[16], bh[16];
3246 PackHolding(wh, white_holding);
3247 PackHolding(bh, black_holding);
3248 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3249 gameInfo.white, gameInfo.black);
3251 sprintf(str, "%s [%s] vs. %s [%s]",
3252 gameInfo.white, white_holding,
3253 gameInfo.black, black_holding);
3256 DrawPosition(FALSE, boards[currentMove]);
3259 /* Suppress following prompt */
3260 if (looking_at(buf, &i, "*% ")) {
3261 savingComment = FALSE;
3268 i++; /* skip unparsed character and loop back */
3271 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3272 started != STARTED_HOLDINGS && i > next_out) {
3273 SendToPlayer(&buf[next_out], i - next_out);
3276 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3278 leftover_len = buf_len - leftover_start;
3279 /* if buffer ends with something we couldn't parse,
3280 reparse it after appending the next read */
3282 } else if (count == 0) {
3283 RemoveInputSource(isr);
3284 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3286 DisplayFatalError(_("Error reading from ICS"), error, 1);
3291 /* Board style 12 looks like this:
3293 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3295 * The "<12> " is stripped before it gets to this routine. The two
3296 * trailing 0's (flip state and clock ticking) are later addition, and
3297 * some chess servers may not have them, or may have only the first.
3298 * Additional trailing fields may be added in the future.
3301 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3303 #define RELATION_OBSERVING_PLAYED 0
3304 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3305 #define RELATION_PLAYING_MYMOVE 1
3306 #define RELATION_PLAYING_NOTMYMOVE -1
3307 #define RELATION_EXAMINING 2
3308 #define RELATION_ISOLATED_BOARD -3
3309 #define RELATION_STARTING_POSITION -4 /* FICS only */
3312 ParseBoard12(string)
3315 GameMode newGameMode;
3316 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3317 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3318 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3319 char to_play, board_chars[200];
3320 char move_str[500], str[500], elapsed_time[500];
3321 char black[32], white[32];
3323 int prevMove = currentMove;
3326 int fromX, fromY, toX, toY;
3328 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3329 char *bookHit = NULL; // [HGM] book
3331 fromX = fromY = toX = toY = -1;
3335 if (appData.debugMode)
3336 fprintf(debugFP, _("Parsing board: %s\n"), string);
3338 move_str[0] = NULLCHAR;
3339 elapsed_time[0] = NULLCHAR;
3340 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3342 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3343 if(string[i] == ' ') { ranks++; files = 0; }
3347 for(j = 0; j <i; j++) board_chars[j] = string[j];
3348 board_chars[i] = '\0';
3351 n = sscanf(string, PATTERN, &to_play, &double_push,
3352 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3353 &gamenum, white, black, &relation, &basetime, &increment,
3354 &white_stren, &black_stren, &white_time, &black_time,
3355 &moveNum, str, elapsed_time, move_str, &ics_flip,
3359 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3360 DisplayError(str, 0);
3364 /* Convert the move number to internal form */
3365 moveNum = (moveNum - 1) * 2;
3366 if (to_play == 'B') moveNum++;
3367 if (moveNum >= MAX_MOVES) {
3368 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3374 case RELATION_OBSERVING_PLAYED:
3375 case RELATION_OBSERVING_STATIC:
3376 if (gamenum == -1) {
3377 /* Old ICC buglet */
3378 relation = RELATION_OBSERVING_STATIC;
3380 newGameMode = IcsObserving;
3382 case RELATION_PLAYING_MYMOVE:
3383 case RELATION_PLAYING_NOTMYMOVE:
3385 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3386 IcsPlayingWhite : IcsPlayingBlack;
3388 case RELATION_EXAMINING:
3389 newGameMode = IcsExamining;
3391 case RELATION_ISOLATED_BOARD:
3393 /* Just display this board. If user was doing something else,
3394 we will forget about it until the next board comes. */
3395 newGameMode = IcsIdle;
3397 case RELATION_STARTING_POSITION:
3398 newGameMode = gameMode;
3402 /* Modify behavior for initial board display on move listing
3405 switch (ics_getting_history) {
3409 case H_GOT_REQ_HEADER:
3410 case H_GOT_UNREQ_HEADER:
3411 /* This is the initial position of the current game */
3412 gamenum = ics_gamenum;
3413 moveNum = 0; /* old ICS bug workaround */
3414 if (to_play == 'B') {
3415 startedFromSetupPosition = TRUE;
3416 blackPlaysFirst = TRUE;
3418 if (forwardMostMove == 0) forwardMostMove = 1;
3419 if (backwardMostMove == 0) backwardMostMove = 1;
3420 if (currentMove == 0) currentMove = 1;
3422 newGameMode = gameMode;
3423 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3425 case H_GOT_UNWANTED_HEADER:
3426 /* This is an initial board that we don't want */
3428 case H_GETTING_MOVES:
3429 /* Should not happen */
3430 DisplayError(_("Error gathering move list: extra board"), 0);
3431 ics_getting_history = H_FALSE;
3435 /* Take action if this is the first board of a new game, or of a
3436 different game than is currently being displayed. */
3437 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3438 relation == RELATION_ISOLATED_BOARD) {
3440 /* Forget the old game and get the history (if any) of the new one */
3441 if (gameMode != BeginningOfGame) {
3445 if (appData.autoRaiseBoard) BoardToTop();
3447 if (gamenum == -1) {
3448 newGameMode = IcsIdle;
3449 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3450 appData.getMoveList) {
3451 /* Need to get game history */
3452 ics_getting_history = H_REQUESTED;
3453 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3457 /* Initially flip the board to have black on the bottom if playing
3458 black or if the ICS flip flag is set, but let the user change
3459 it with the Flip View button. */
3460 flipView = appData.autoFlipView ?
3461 (newGameMode == IcsPlayingBlack) || ics_flip :
3464 /* Done with values from previous mode; copy in new ones */
3465 gameMode = newGameMode;
3467 ics_gamenum = gamenum;
3468 if (gamenum == gs_gamenum) {
3469 int klen = strlen(gs_kind);
3470 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3471 sprintf(str, "ICS %s", gs_kind);
3472 gameInfo.event = StrSave(str);
3474 gameInfo.event = StrSave("ICS game");
3476 gameInfo.site = StrSave(appData.icsHost);
3477 gameInfo.date = PGNDate();
3478 gameInfo.round = StrSave("-");
3479 gameInfo.white = StrSave(white);
3480 gameInfo.black = StrSave(black);
3481 timeControl = basetime * 60 * 1000;
3483 timeIncrement = increment * 1000;
3484 movesPerSession = 0;
3485 gameInfo.timeControl = TimeControlTagValue();
3486 VariantSwitch(board, StringToVariant(gameInfo.event) );
3487 if (appData.debugMode) {
3488 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3489 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3490 setbuf(debugFP, NULL);
3493 gameInfo.outOfBook = NULL;
3495 /* Do we have the ratings? */
3496 if (strcmp(player1Name, white) == 0 &&
3497 strcmp(player2Name, black) == 0) {
3498 if (appData.debugMode)
3499 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3500 player1Rating, player2Rating);
3501 gameInfo.whiteRating = player1Rating;
3502 gameInfo.blackRating = player2Rating;
3503 } else if (strcmp(player2Name, white) == 0 &&
3504 strcmp(player1Name, black) == 0) {
3505 if (appData.debugMode)
3506 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3507 player2Rating, player1Rating);
3508 gameInfo.whiteRating = player2Rating;
3509 gameInfo.blackRating = player1Rating;
3511 player1Name[0] = player2Name[0] = NULLCHAR;
3513 /* Silence shouts if requested */
3514 if (appData.quietPlay &&
3515 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3516 SendToICS(ics_prefix);
3517 SendToICS("set shout 0\n");
3521 /* Deal with midgame name changes */
3523 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3524 if (gameInfo.white) free(gameInfo.white);
3525 gameInfo.white = StrSave(white);
3527 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3528 if (gameInfo.black) free(gameInfo.black);
3529 gameInfo.black = StrSave(black);
3533 /* Throw away game result if anything actually changes in examine mode */
3534 if (gameMode == IcsExamining && !newGame) {
3535 gameInfo.result = GameUnfinished;
3536 if (gameInfo.resultDetails != NULL) {
3537 free(gameInfo.resultDetails);
3538 gameInfo.resultDetails = NULL;
3542 /* In pausing && IcsExamining mode, we ignore boards coming
3543 in if they are in a different variation than we are. */
3544 if (pauseExamInvalid) return;
3545 if (pausing && gameMode == IcsExamining) {
3546 if (moveNum <= pauseExamForwardMostMove) {
3547 pauseExamInvalid = TRUE;
3548 forwardMostMove = pauseExamForwardMostMove;
3553 if (appData.debugMode) {
3554 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3556 /* Parse the board */
3557 for (k = 0; k < ranks; k++) {
3558 for (j = 0; j < files; j++)
3559 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3560 if(gameInfo.holdingsWidth > 1) {
3561 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3562 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3565 CopyBoard(boards[moveNum], board);
3567 startedFromSetupPosition =
3568 !CompareBoards(board, initialPosition);
3569 if(startedFromSetupPosition)
3570 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3573 /* [HGM] Set castling rights. Take the outermost Rooks,
3574 to make it also work for FRC opening positions. Note that board12
3575 is really defective for later FRC positions, as it has no way to
3576 indicate which Rook can castle if they are on the same side of King.
3577 For the initial position we grant rights to the outermost Rooks,
3578 and remember thos rights, and we then copy them on positions
3579 later in an FRC game. This means WB might not recognize castlings with
3580 Rooks that have moved back to their original position as illegal,
3581 but in ICS mode that is not its job anyway.
3583 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3584 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3586 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3587 if(board[0][i] == WhiteRook) j = i;
3588 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3590 if(board[0][i] == WhiteRook) j = i;
3591 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3596 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3599 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3600 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3602 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3603 if(board[BOARD_HEIGHT-1][k] == bKing)
3604 initialRights[5] = castlingRights[moveNum][5] = k;
3606 r = castlingRights[moveNum][0] = initialRights[0];
3607 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3608 r = castlingRights[moveNum][1] = initialRights[1];
3609 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3610 r = castlingRights[moveNum][3] = initialRights[3];
3611 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3612 r = castlingRights[moveNum][4] = initialRights[4];
3613 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3614 /* wildcastle kludge: always assume King has rights */
3615 r = castlingRights[moveNum][2] = initialRights[2];
3616 r = castlingRights[moveNum][5] = initialRights[5];
3618 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3619 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3622 if (ics_getting_history == H_GOT_REQ_HEADER ||
3623 ics_getting_history == H_GOT_UNREQ_HEADER) {
3624 /* This was an initial position from a move list, not
3625 the current position */
3629 /* Update currentMove and known move number limits */
3630 newMove = newGame || moveNum > forwardMostMove;
3632 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3633 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3634 takeback = forwardMostMove - moveNum;
3635 for (i = 0; i < takeback; i++) {
3636 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3637 SendToProgram("undo\n", &first);
3642 forwardMostMove = backwardMostMove = currentMove = moveNum;
3643 if (gameMode == IcsExamining && moveNum == 0) {
3644 /* Workaround for ICS limitation: we are not told the wild
3645 type when starting to examine a game. But if we ask for
3646 the move list, the move list header will tell us */
3647 ics_getting_history = H_REQUESTED;
3648 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3651 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3652 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3653 forwardMostMove = moveNum;
3654 if (!pausing || currentMove > forwardMostMove)
3655 currentMove = forwardMostMove;
3657 /* New part of history that is not contiguous with old part */
3658 if (pausing && gameMode == IcsExamining) {
3659 pauseExamInvalid = TRUE;
3660 forwardMostMove = pauseExamForwardMostMove;
3663 forwardMostMove = backwardMostMove = currentMove = moveNum;
3664 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3665 ics_getting_history = H_REQUESTED;
3666 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3671 /* Update the clocks */
3672 if (strchr(elapsed_time, '.')) {
3674 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3675 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3677 /* Time is in seconds */
3678 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3679 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3684 if (appData.zippyPlay && newGame &&
3685 gameMode != IcsObserving && gameMode != IcsIdle &&
3686 gameMode != IcsExamining)
3687 ZippyFirstBoard(moveNum, basetime, increment);
3690 /* Put the move on the move list, first converting
3691 to canonical algebraic form. */
3693 if (appData.debugMode) {
3694 if (appData.debugMode) { int f = forwardMostMove;
3695 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3696 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3698 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3699 fprintf(debugFP, "moveNum = %d\n", moveNum);
3700 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3701 setbuf(debugFP, NULL);
3703 if (moveNum <= backwardMostMove) {
3704 /* We don't know what the board looked like before
3706 strcpy(parseList[moveNum - 1], move_str);
3707 strcat(parseList[moveNum - 1], " ");
3708 strcat(parseList[moveNum - 1], elapsed_time);
3709 moveList[moveNum - 1][0] = NULLCHAR;
3710 } else if (strcmp(move_str, "none") == 0) {
3711 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3712 /* Again, we don't know what the board looked like;
3713 this is really the start of the game. */
3714 parseList[moveNum - 1][0] = NULLCHAR;
3715 moveList[moveNum - 1][0] = NULLCHAR;
3716 backwardMostMove = moveNum;
3717 startedFromSetupPosition = TRUE;
3718 fromX = fromY = toX = toY = -1;
3720 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3721 // So we parse the long-algebraic move string in stead of the SAN move
3722 int valid; char buf[MSG_SIZ], *prom;
3724 // str looks something like "Q/a1-a2"; kill the slash
3726 sprintf(buf, "%c%s", str[0], str+2);
3727 else strcpy(buf, str); // might be castling
3728 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3729 strcat(buf, prom); // long move lacks promo specification!
3730 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3731 if(appData.debugMode)
3732 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3733 strcpy(move_str, buf);
3735 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3736 &fromX, &fromY, &toX, &toY, &promoChar)
3737 || ParseOneMove(buf, moveNum - 1, &moveType,
3738 &fromX, &fromY, &toX, &toY, &promoChar);
3739 // end of long SAN patch
3741 (void) CoordsToAlgebraic(boards[moveNum - 1],
3742 PosFlags(moveNum - 1), EP_UNKNOWN,
3743 fromY, fromX, toY, toX, promoChar,
3744 parseList[moveNum-1]);
3745 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3746 castlingRights[moveNum]) ) {
3752 if(gameInfo.variant != VariantShogi)
3753 strcat(parseList[moveNum - 1], "+");
3756 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3757 strcat(parseList[moveNum - 1], "#");
3760 strcat(parseList[moveNum - 1], " ");
3761 strcat(parseList[moveNum - 1], elapsed_time);
3762 /* currentMoveString is set as a side-effect of ParseOneMove */
3763 strcpy(moveList[moveNum - 1], currentMoveString);
3764 strcat(moveList[moveNum - 1], "\n");
3766 /* Move from ICS was illegal!? Punt. */
3767 if (appData.debugMode) {
3768 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3769 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3772 if (appData.testLegality && appData.debugMode) {
3773 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3774 DisplayError(str, 0);
3777 strcpy(parseList[moveNum - 1], move_str);
3778 strcat(parseList[moveNum - 1], " ");
3779 strcat(parseList[moveNum - 1], elapsed_time);
3780 moveList[moveNum - 1][0] = NULLCHAR;
3781 fromX = fromY = toX = toY = -1;
3784 if (appData.debugMode) {
3785 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3786 setbuf(debugFP, NULL);
3790 /* Send move to chess program (BEFORE animating it). */
3791 if (appData.zippyPlay && !newGame && newMove &&
3792 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3794 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3795 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3796 if (moveList[moveNum - 1][0] == NULLCHAR) {
3797 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3799 DisplayError(str, 0);
3801 if (first.sendTime) {
3802 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3804 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3805 if (firstMove && !bookHit) {
3807 if (first.useColors) {
3808 SendToProgram(gameMode == IcsPlayingWhite ?
3810 "black\ngo\n", &first);
3812 SendToProgram("go\n", &first);
3814 first.maybeThinking = TRUE;
3817 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3818 if (moveList[moveNum - 1][0] == NULLCHAR) {
3819 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3820 DisplayError(str, 0);
3822 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3823 SendMoveToProgram(moveNum - 1, &first);