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>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
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 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char epStatus[MAX_MOVES];
445 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 ChessSquare FIDEArray[2][BOARD_SIZE] = {
457 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467 BlackKing, BlackKing, BlackKnight, BlackRook }
470 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473 { BlackRook, BlackMan, BlackBishop, BlackQueen,
474 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481 BlackKing, BlackBishop, BlackKnight, BlackRook }
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 #define GothicArray CapablancaArray
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 #define FalconArray CapablancaArray
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
569 Board initialPosition;
572 /* Convert str to a rating. Checks for special cases of "----",
574 "++++", etc. Also strips ()'s */
576 string_to_rating(str)
579 while(*str && !isdigit(*str)) ++str;
581 return 0; /* One of the special "no rating" cases */
589 /* Init programStats */
590 programStats.movelist[0] = 0;
591 programStats.depth = 0;
592 programStats.nr_moves = 0;
593 programStats.moves_left = 0;
594 programStats.nodes = 0;
595 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
596 programStats.score = 0;
597 programStats.got_only_move = 0;
598 programStats.got_fail = 0;
599 programStats.line_is_book = 0;
605 int matched, min, sec;
607 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609 GetTimeMark(&programStartTime);
610 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
613 programStats.ok_to_send = 1;
614 programStats.seen_stat = 0;
617 * Initialize game list
623 * Internet chess server status
625 if (appData.icsActive) {
626 appData.matchMode = FALSE;
627 appData.matchGames = 0;
629 appData.noChessProgram = !appData.zippyPlay;
631 appData.zippyPlay = FALSE;
632 appData.zippyTalk = FALSE;
633 appData.noChessProgram = TRUE;
635 if (*appData.icsHelper != NULLCHAR) {
636 appData.useTelnet = TRUE;
637 appData.telnetProgram = appData.icsHelper;
640 appData.zippyTalk = appData.zippyPlay = FALSE;
643 /* [AS] Initialize pv info list [HGM] and game state */
647 for( i=0; i<MAX_MOVES; i++ ) {
648 pvInfoList[i].depth = -1;
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
655 * Parse timeControl resource
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658 appData.movesPerSession)) {
660 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661 DisplayFatalError(buf, 0, 2);
665 * Parse searchTime resource
667 if (*appData.searchTime != NULLCHAR) {
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670 searchTime = min * 60;
671 } else if (matched == 2) {
672 searchTime = min * 60 + sec;
675 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676 DisplayFatalError(buf, 0, 2);
680 /* [AS] Adjudication threshold */
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
683 first.which = "first";
684 second.which = "second";
685 first.maybeThinking = second.maybeThinking = FALSE;
686 first.pr = second.pr = NoProc;
687 first.isr = second.isr = NULL;
688 first.sendTime = second.sendTime = 2;
689 first.sendDrawOffers = 1;
690 if (appData.firstPlaysBlack) {
691 first.twoMachinesColor = "black\n";
692 second.twoMachinesColor = "white\n";
694 first.twoMachinesColor = "white\n";
695 second.twoMachinesColor = "black\n";
697 first.program = appData.firstChessProgram;
698 second.program = appData.secondChessProgram;
699 first.host = appData.firstHost;
700 second.host = appData.secondHost;
701 first.dir = appData.firstDirectory;
702 second.dir = appData.secondDirectory;
703 first.other = &second;
704 second.other = &first;
705 first.initString = appData.initString;
706 second.initString = appData.secondInitString;
707 first.computerString = appData.firstComputerString;
708 second.computerString = appData.secondComputerString;
709 first.useSigint = second.useSigint = TRUE;
710 first.useSigterm = second.useSigterm = TRUE;
711 first.reuse = appData.reuseFirst;
712 second.reuse = appData.reuseSecond;
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
714 second.nps = appData.secondNPS;
715 first.useSetboard = second.useSetboard = FALSE;
716 first.useSAN = second.useSAN = FALSE;
717 first.usePing = second.usePing = FALSE;
718 first.lastPing = second.lastPing = 0;
719 first.lastPong = second.lastPong = 0;
720 first.usePlayother = second.usePlayother = FALSE;
721 first.useColors = second.useColors = TRUE;
722 first.useUsermove = second.useUsermove = FALSE;
723 first.sendICS = second.sendICS = FALSE;
724 first.sendName = second.sendName = appData.icsActive;
725 first.sdKludge = second.sdKludge = FALSE;
726 first.stKludge = second.stKludge = FALSE;
727 TidyProgramName(first.program, first.host, first.tidy);
728 TidyProgramName(second.program, second.host, second.tidy);
729 first.matchWins = second.matchWins = 0;
730 strcpy(first.variants, appData.variant);
731 strcpy(second.variants, appData.variant);
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
733 first.analyzing = second.analyzing = FALSE;
734 first.initDone = second.initDone = FALSE;
736 /* New features added by Tord: */
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739 /* End of new features added by Tord. */
740 first.fenOverride = appData.fenOverride1;
741 second.fenOverride = appData.fenOverride2;
743 /* [HGM] time odds: set factor for each machine */
744 first.timeOdds = appData.firstTimeOdds;
745 second.timeOdds = appData.secondTimeOdds;
747 if(appData.timeOddsMode) {
748 norm = first.timeOdds;
749 if(norm > second.timeOdds) norm = second.timeOdds;
751 first.timeOdds /= norm;
752 second.timeOdds /= norm;
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756 first.accumulateTC = appData.firstAccumulateTC;
757 second.accumulateTC = appData.secondAccumulateTC;
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
761 first.debug = second.debug = FALSE;
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
765 first.optionSettings = appData.firstOptions;
766 second.optionSettings = appData.secondOptions;
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770 first.isUCI = appData.firstIsUCI; /* [AS] */
771 second.isUCI = appData.secondIsUCI; /* [AS] */
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775 if (appData.firstProtocolVersion > PROTOVER ||
776 appData.firstProtocolVersion < 1) {
778 sprintf(buf, _("protocol version %d not supported"),
779 appData.firstProtocolVersion);
780 DisplayFatalError(buf, 0, 2);
782 first.protocolVersion = appData.firstProtocolVersion;
785 if (appData.secondProtocolVersion > PROTOVER ||
786 appData.secondProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.secondProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 second.protocolVersion = appData.secondProtocolVersion;
795 if (appData.icsActive) {
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798 appData.clockMode = FALSE;
799 first.sendTime = second.sendTime = 0;
803 /* Override some settings from environment variables, for backward
804 compatibility. Unfortunately it's not feasible to have the env
805 vars just set defaults, at least in xboard. Ugh.
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
812 if (appData.noChessProgram) {
813 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814 sprintf(programVersion, "%s", PACKAGE_STRING);
816 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821 if (!appData.icsActive) {
823 /* Check for variants that are supported only in ICS mode,
824 or not at all. Some that are accepted here nevertheless
825 have bugs; see comments below.
827 VariantClass variant = StringToVariant(appData.variant);
829 case VariantBughouse: /* need four players and two boards */
830 case VariantKriegspiel: /* need to hide pieces and move details */
831 /* case VariantFischeRandom: (Fabien: moved below) */
832 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833 DisplayFatalError(buf, 0, 2);
837 case VariantLoadable:
847 sprintf(buf, _("Unknown variant name %s"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
851 case VariantXiangqi: /* [HGM] repetition rules not implemented */
852 case VariantFairy: /* [HGM] TestLegality definitely off! */
853 case VariantGothic: /* [HGM] should work */
854 case VariantCapablanca: /* [HGM] should work */
855 case VariantCourier: /* [HGM] initial forced moves not implemented */
856 case VariantShogi: /* [HGM] drops not tested for legality */
857 case VariantKnightmate: /* [HGM] should work */
858 case VariantCylinder: /* [HGM] untested */
859 case VariantFalcon: /* [HGM] untested */
860 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861 offboard interposition not understood */
862 case VariantNormal: /* definitely works! */
863 case VariantWildCastle: /* pieces not automatically shuffled */
864 case VariantNoCastle: /* pieces not automatically shuffled */
865 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866 case VariantLosers: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantSuicide: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantGiveaway: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantTwoKings: /* should work */
873 case VariantAtomic: /* should work except for win condition */
874 case Variant3Check: /* should work except for win condition */
875 case VariantShatranj: /* should work except for all win conditions */
876 case VariantBerolina: /* might work if TestLegality is off */
877 case VariantCapaRandom: /* should work */
878 case VariantJanus: /* should work */
879 case VariantSuper: /* experimental */
880 case VariantGreat: /* experimental, requires legality testing to be off */
885 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
886 InitEngineUCI( installDir, &second );
889 int NextIntegerFromString( char ** str, long * value )
894 while( *s == ' ' || *s == '\t' ) {
900 if( *s >= '0' && *s <= '9' ) {
901 while( *s >= '0' && *s <= '9' ) {
902 *value = *value * 10 + (*s - '0');
914 int NextTimeControlFromString( char ** str, long * value )
917 int result = NextIntegerFromString( str, &temp );
920 *value = temp * 60; /* Minutes */
923 result = NextIntegerFromString( str, &temp );
924 *value += temp; /* Seconds */
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 { /* [HGM] routine added to read '+moves/time' for secondary time control */
933 int result = -1; long temp, temp2;
935 if(**str != '+') return -1; // old params remain in force!
937 if( NextTimeControlFromString( str, &temp ) ) return -1;
940 /* time only: incremental or sudden-death time control */
941 if(**str == '+') { /* increment follows; read it */
943 if(result = NextIntegerFromString( str, &temp2)) return -1;
946 *moves = 0; *tc = temp * 1000;
948 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
950 (*str)++; /* classical time control */
951 result = NextTimeControlFromString( str, &temp2);
960 int GetTimeQuota(int movenr)
961 { /* [HGM] get time to add from the multi-session time-control string */
962 int moves=1; /* kludge to force reading of first session */
963 long time, increment;
964 char *s = fullTimeControlString;
966 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970 if(movenr == -1) return time; /* last move before new session */
971 if(!moves) return increment; /* current session is incremental */
972 if(movenr >= 0) movenr -= moves; /* we already finished this session */
973 } while(movenr >= -1); /* try again for next session */
975 return 0; // no new time quota on this move
979 ParseTimeControl(tc, ti, mps)
988 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992 else sprintf(buf, "+%s+%d", tc, ti);
995 sprintf(buf, "+%d/%s", mps, tc);
996 else sprintf(buf, "+%s", tc);
998 fullTimeControlString = StrSave(buf);
1000 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1005 /* Parse second time control */
1008 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1016 timeControl_2 = tc2 * 1000;
1026 timeControl = tc1 * 1000;
1029 timeIncrement = ti * 1000; /* convert to ms */
1030 movesPerSession = 0;
1033 movesPerSession = mps;
1041 if (appData.debugMode) {
1042 fprintf(debugFP, "%s\n", programVersion);
1045 set_cont_sequence(appData.wrapContSeq);
1046 if (appData.matchGames > 0) {
1047 appData.matchMode = TRUE;
1048 } else if (appData.matchMode) {
1049 appData.matchGames = 1;
1051 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052 appData.matchGames = appData.sameColorGames;
1053 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058 if (appData.noChessProgram || first.protocolVersion == 1) {
1061 /* kludge: allow timeout for initial "feature" commands */
1063 DisplayMessage("", _("Starting chess program"));
1064 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1069 InitBackEnd3 P((void))
1071 GameMode initialMode;
1075 InitChessProgram(&first, startedFromSetupPosition);
1078 if (appData.icsActive) {
1080 /* [DM] Make a console window if needed [HGM] merged ifs */
1085 if (*appData.icsCommPort != NULLCHAR) {
1086 sprintf(buf, _("Could not open comm port %s"),
1087 appData.icsCommPort);
1089 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090 appData.icsHost, appData.icsPort);
1092 DisplayFatalError(buf, err, 1);
1097 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1099 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100 } else if (appData.noChessProgram) {
1106 if (*appData.cmailGameName != NULLCHAR) {
1108 OpenLoopback(&cmailPR);
1110 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1114 DisplayMessage("", "");
1115 if (StrCaseCmp(appData.initialMode, "") == 0) {
1116 initialMode = BeginningOfGame;
1117 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118 initialMode = TwoMachinesPlay;
1119 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120 initialMode = AnalyzeFile;
1121 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122 initialMode = AnalyzeMode;
1123 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124 initialMode = MachinePlaysWhite;
1125 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126 initialMode = MachinePlaysBlack;
1127 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128 initialMode = EditGame;
1129 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130 initialMode = EditPosition;
1131 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132 initialMode = Training;
1134 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135 DisplayFatalError(buf, 0, 2);
1139 if (appData.matchMode) {
1140 /* Set up machine vs. machine match */
1141 if (appData.noChessProgram) {
1142 DisplayFatalError(_("Can't have a match with no chess programs"),
1148 if (*appData.loadGameFile != NULLCHAR) {
1149 int index = appData.loadGameIndex; // [HGM] autoinc
1150 if(index<0) lastIndex = index = 1;
1151 if (!LoadGameFromFile(appData.loadGameFile,
1153 appData.loadGameFile, FALSE)) {
1154 DisplayFatalError(_("Bad game file"), 0, 1);
1157 } else if (*appData.loadPositionFile != NULLCHAR) {
1158 int index = appData.loadPositionIndex; // [HGM] autoinc
1159 if(index<0) lastIndex = index = 1;
1160 if (!LoadPositionFromFile(appData.loadPositionFile,
1162 appData.loadPositionFile)) {
1163 DisplayFatalError(_("Bad position file"), 0, 1);
1168 } else if (*appData.cmailGameName != NULLCHAR) {
1169 /* Set up cmail mode */
1170 ReloadCmailMsgEvent(TRUE);
1172 /* Set up other modes */
1173 if (initialMode == AnalyzeFile) {
1174 if (*appData.loadGameFile == NULLCHAR) {
1175 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 (void) LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameIndex,
1182 appData.loadGameFile, TRUE);
1183 } else if (*appData.loadPositionFile != NULLCHAR) {
1184 (void) LoadPositionFromFile(appData.loadPositionFile,
1185 appData.loadPositionIndex,
1186 appData.loadPositionFile);
1187 /* [HGM] try to make self-starting even after FEN load */
1188 /* to allow automatic setup of fairy variants with wtm */
1189 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190 gameMode = BeginningOfGame;
1191 setboardSpoiledMachineBlack = 1;
1193 /* [HGM] loadPos: make that every new game uses the setup */
1194 /* from file as long as we do not switch variant */
1195 if(!blackPlaysFirst) { int i;
1196 startedFromPositionFile = TRUE;
1197 CopyBoard(filePosition, boards[0]);
1198 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1201 if (initialMode == AnalyzeMode) {
1202 if (appData.noChessProgram) {
1203 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1206 if (appData.icsActive) {
1207 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1211 } else if (initialMode == AnalyzeFile) {
1212 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213 ShowThinkingEvent();
1215 AnalysisPeriodicEvent(1);
1216 } else if (initialMode == MachinePlaysWhite) {
1217 if (appData.noChessProgram) {
1218 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1222 if (appData.icsActive) {
1223 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1227 MachineWhiteEvent();
1228 } else if (initialMode == MachinePlaysBlack) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1239 MachineBlackEvent();
1240 } else if (initialMode == TwoMachinesPlay) {
1241 if (appData.noChessProgram) {
1242 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252 } else if (initialMode == EditGame) {
1254 } else if (initialMode == EditPosition) {
1255 EditPositionEvent();
1256 } else if (initialMode == Training) {
1257 if (*appData.loadGameFile == NULLCHAR) {
1258 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1267 * Establish will establish a contact to a remote host.port.
1268 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269 * used to talk to the host.
1270 * Returns 0 if okay, error code if not.
1277 if (*appData.icsCommPort != NULLCHAR) {
1278 /* Talk to the host through a serial comm port */
1279 return OpenCommPort(appData.icsCommPort, &icsPR);
1281 } else if (*appData.gateway != NULLCHAR) {
1282 if (*appData.remoteShell == NULLCHAR) {
1283 /* Use the rcmd protocol to run telnet program on a gateway host */
1284 snprintf(buf, sizeof(buf), "%s %s %s",
1285 appData.telnetProgram, appData.icsHost, appData.icsPort);
1286 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1289 /* Use the rsh program to run telnet program on a gateway host */
1290 if (*appData.remoteUser == NULLCHAR) {
1291 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292 appData.gateway, appData.telnetProgram,
1293 appData.icsHost, appData.icsPort);
1295 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296 appData.remoteShell, appData.gateway,
1297 appData.remoteUser, appData.telnetProgram,
1298 appData.icsHost, appData.icsPort);
1300 return StartChildProcess(buf, "", &icsPR);
1303 } else if (appData.useTelnet) {
1304 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1307 /* TCP socket interface differs somewhat between
1308 Unix and NT; handle details in the front end.
1310 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315 show_bytes(fp, buf, count)
1321 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322 fprintf(fp, "\\%03o", *buf & 0xff);
1331 /* Returns an errno value */
1333 OutputMaybeTelnet(pr, message, count, outError)
1339 char buf[8192], *p, *q, *buflim;
1340 int left, newcount, outcount;
1342 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343 *appData.gateway != NULLCHAR) {
1344 if (appData.debugMode) {
1345 fprintf(debugFP, ">ICS: ");
1346 show_bytes(debugFP, message, count);
1347 fprintf(debugFP, "\n");
1349 return OutputToProcess(pr, message, count, outError);
1352 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1359 if (appData.debugMode) {
1360 fprintf(debugFP, ">ICS: ");
1361 show_bytes(debugFP, buf, newcount);
1362 fprintf(debugFP, "\n");
1364 outcount = OutputToProcess(pr, buf, newcount, outError);
1365 if (outcount < newcount) return -1; /* to be sure */
1372 } else if (((unsigned char) *p) == TN_IAC) {
1373 *q++ = (char) TN_IAC;
1380 if (appData.debugMode) {
1381 fprintf(debugFP, ">ICS: ");
1382 show_bytes(debugFP, buf, newcount);
1383 fprintf(debugFP, "\n");
1385 outcount = OutputToProcess(pr, buf, newcount, outError);
1386 if (outcount < newcount) return -1; /* to be sure */
1391 read_from_player(isr, closure, message, count, error)
1398 int outError, outCount;
1399 static int gotEof = 0;
1401 /* Pass data read from player on to ICS */
1404 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405 if (outCount < count) {
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1408 } else if (count < 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411 } else if (gotEof++ > 0) {
1412 RemoveInputSource(isr);
1413 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1419 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420 SendToICS("date\n");
1421 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1427 char buffer[MSG_SIZ];
1430 va_start(args, format);
1431 vsnprintf(buffer, sizeof(buffer), format, args);
1432 buffer[sizeof(buffer)-1] = '\0';
1441 int count, outCount, outError;
1443 if (icsPR == NULL) return;
1446 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447 if (outCount < count) {
1448 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1452 /* This is used for sending logon scripts to the ICS. Sending
1453 without a delay causes problems when using timestamp on ICC
1454 (at least on my machine). */
1456 SendToICSDelayed(s,msdelay)
1460 int count, outCount, outError;
1462 if (icsPR == NULL) return;
1465 if (appData.debugMode) {
1466 fprintf(debugFP, ">ICS: ");
1467 show_bytes(debugFP, s, count);
1468 fprintf(debugFP, "\n");
1470 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1472 if (outCount < count) {
1473 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478 /* Remove all highlighting escape sequences in s
1479 Also deletes any suffix starting with '('
1482 StripHighlightAndTitle(s)
1485 static char retbuf[MSG_SIZ];
1488 while (*s != NULLCHAR) {
1489 while (*s == '\033') {
1490 while (*s != NULLCHAR && !isalpha(*s)) s++;
1491 if (*s != NULLCHAR) s++;
1493 while (*s != NULLCHAR && *s != '\033') {
1494 if (*s == '(' || *s == '[') {
1505 /* Remove all highlighting escape sequences in s */
1510 static char retbuf[MSG_SIZ];
1513 while (*s != NULLCHAR) {
1514 while (*s == '\033') {
1515 while (*s != NULLCHAR && !isalpha(*s)) s++;
1516 if (*s != NULLCHAR) s++;
1518 while (*s != NULLCHAR && *s != '\033') {
1526 char *variantNames[] = VARIANT_NAMES;
1531 return variantNames[v];
1535 /* Identify a variant from the strings the chess servers use or the
1536 PGN Variant tag names we use. */
1543 VariantClass v = VariantNormal;
1544 int i, found = FALSE;
1549 /* [HGM] skip over optional board-size prefixes */
1550 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552 while( *e++ != '_');
1555 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1559 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560 if (StrCaseStr(e, variantNames[i])) {
1561 v = (VariantClass) i;
1568 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569 || StrCaseStr(e, "wild/fr")
1570 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571 v = VariantFischeRandom;
1572 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573 (i = 1, p = StrCaseStr(e, "w"))) {
1575 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582 case 0: /* FICS only, actually */
1584 /* Castling legal even if K starts on d-file */
1585 v = VariantWildCastle;
1590 /* Castling illegal even if K & R happen to start in
1591 normal positions. */
1592 v = VariantNoCastle;
1605 /* Castling legal iff K & R start in normal positions */
1611 /* Special wilds for position setup; unclear what to do here */
1612 v = VariantLoadable;
1615 /* Bizarre ICC game */
1616 v = VariantTwoKings;
1619 v = VariantKriegspiel;
1625 v = VariantFischeRandom;
1628 v = VariantCrazyhouse;
1631 v = VariantBughouse;
1637 /* Not quite the same as FICS suicide! */
1638 v = VariantGiveaway;
1644 v = VariantShatranj;
1647 /* Temporary names for future ICC types. The name *will* change in
1648 the next xboard/WinBoard release after ICC defines it. */
1686 v = VariantCapablanca;
1689 v = VariantKnightmate;
1695 v = VariantCylinder;
1701 v = VariantCapaRandom;
1704 v = VariantBerolina;
1716 /* Found "wild" or "w" in the string but no number;
1717 must assume it's normal chess. */
1721 sprintf(buf, _("Unknown wild type %d"), wnum);
1722 DisplayError(buf, 0);
1728 if (appData.debugMode) {
1729 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730 e, wnum, VariantName(v));
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739 advance *index beyond it, and set leftover_start to the new value of
1740 *index; else return FALSE. If pattern contains the character '*', it
1741 matches any sequence of characters not containing '\r', '\n', or the
1742 character following the '*' (if any), and the matched sequence(s) are
1743 copied into star_match.
1746 looking_at(buf, index, pattern)
1751 char *bufp = &buf[*index], *patternp = pattern;
1753 char *matchp = star_match[0];
1756 if (*patternp == NULLCHAR) {
1757 *index = leftover_start = bufp - buf;
1761 if (*bufp == NULLCHAR) return FALSE;
1762 if (*patternp == '*') {
1763 if (*bufp == *(patternp + 1)) {
1765 matchp = star_match[++star_count];
1769 } else if (*bufp == '\n' || *bufp == '\r') {
1771 if (*patternp == NULLCHAR)
1776 *matchp++ = *bufp++;
1780 if (*patternp != *bufp) return FALSE;
1787 SendToPlayer(data, length)
1791 int error, outCount;
1792 outCount = OutputToProcess(NoProc, data, length, &error);
1793 if (outCount < length) {
1794 DisplayFatalError(_("Error writing to display"), error, 1);
1799 PackHolding(packed, holding)
1811 switch (runlength) {
1822 sprintf(q, "%d", runlength);
1834 /* Telnet protocol requests from the front end */
1836 TelnetRequest(ddww, option)
1837 unsigned char ddww, option;
1839 unsigned char msg[3];
1840 int outCount, outError;
1842 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1844 if (appData.debugMode) {
1845 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861 sprintf(buf1, "%d", ddww);
1870 sprintf(buf2, "%d", option);
1873 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1878 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DO, TN_ECHO);
1894 if (!appData.icsActive) return;
1895 TelnetRequest(TN_DONT, TN_ECHO);
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1901 /* put the holdings sent to us by the server on the board holdings area */
1902 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1906 if(gameInfo.holdingsWidth < 2) return;
1907 if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908 return; // prevent overwriting by pre-board holdings
1910 if( (int)lowestPiece >= BlackPawn ) {
1913 holdingsStartRow = BOARD_HEIGHT-1;
1916 holdingsColumn = BOARD_WIDTH-1;
1917 countsColumn = BOARD_WIDTH-2;
1918 holdingsStartRow = 0;
1922 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923 board[i][holdingsColumn] = EmptySquare;
1924 board[i][countsColumn] = (ChessSquare) 0;
1926 while( (p=*holdings++) != NULLCHAR ) {
1927 piece = CharToPiece( ToUpper(p) );
1928 if(piece == EmptySquare) continue;
1929 /*j = (int) piece - (int) WhitePawn;*/
1930 j = PieceToNumber(piece);
1931 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932 if(j < 0) continue; /* should not happen */
1933 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935 board[holdingsStartRow+j*direction][countsColumn]++;
1941 VariantSwitch(Board board, VariantClass newVariant)
1943 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1946 startedFromPositionFile = FALSE;
1947 if(gameInfo.variant == newVariant) return;
1949 /* [HGM] This routine is called each time an assignment is made to
1950 * gameInfo.variant during a game, to make sure the board sizes
1951 * are set to match the new variant. If that means adding or deleting
1952 * holdings, we shift the playing board accordingly
1953 * This kludge is needed because in ICS observe mode, we get boards
1954 * of an ongoing game without knowing the variant, and learn about the
1955 * latter only later. This can be because of the move list we requested,
1956 * in which case the game history is refilled from the beginning anyway,
1957 * but also when receiving holdings of a crazyhouse game. In the latter
1958 * case we want to add those holdings to the already received position.
1962 if (appData.debugMode) {
1963 fprintf(debugFP, "Switch board from %s to %s\n",
1964 VariantName(gameInfo.variant), VariantName(newVariant));
1965 setbuf(debugFP, NULL);
1967 shuffleOpenings = 0; /* [HGM] shuffle */
1968 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1972 newWidth = 9; newHeight = 9;
1973 gameInfo.holdingsSize = 7;
1974 case VariantBughouse:
1975 case VariantCrazyhouse:
1976 newHoldingsWidth = 2; break;
1980 newHoldingsWidth = 2;
1981 gameInfo.holdingsSize = 8;
1984 case VariantCapablanca:
1985 case VariantCapaRandom:
1988 newHoldingsWidth = gameInfo.holdingsSize = 0;
1991 if(newWidth != gameInfo.boardWidth ||
1992 newHeight != gameInfo.boardHeight ||
1993 newHoldingsWidth != gameInfo.holdingsWidth ) {
1995 /* shift position to new playing area, if needed */
1996 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1997 for(i=0; i<BOARD_HEIGHT; i++)
1998 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1999 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2001 for(i=0; i<newHeight; i++) {
2002 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2003 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2005 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2006 for(i=0; i<BOARD_HEIGHT; i++)
2007 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2008 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011 gameInfo.boardWidth = newWidth;
2012 gameInfo.boardHeight = newHeight;
2013 gameInfo.holdingsWidth = newHoldingsWidth;
2014 gameInfo.variant = newVariant;
2015 InitDrawingSizes(-2, 0);
2016 } else gameInfo.variant = newVariant;
2017 CopyBoard(oldBoard, board); // remember correctly formatted board
2018 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2019 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2022 static int loggedOn = FALSE;
2024 /*-- Game start info cache: --*/
2026 char gs_kind[MSG_SIZ];
2027 static char player1Name[128] = "";
2028 static char player2Name[128] = "";
2029 static char cont_seq[] = "\n\\ ";
2030 static int player1Rating = -1;
2031 static int player2Rating = -1;
2032 /*----------------------------*/
2034 ColorClass curColor = ColorNormal;
2035 int suppressKibitz = 0;
2038 read_from_ics(isr, closure, data, count, error)
2045 #define BUF_SIZE 8192
2046 #define STARTED_NONE 0
2047 #define STARTED_MOVES 1
2048 #define STARTED_BOARD 2
2049 #define STARTED_OBSERVE 3
2050 #define STARTED_HOLDINGS 4
2051 #define STARTED_CHATTER 5
2052 #define STARTED_COMMENT 6
2053 #define STARTED_MOVES_NOHIDE 7
2055 static int started = STARTED_NONE;
2056 static char parse[20000];
2057 static int parse_pos = 0;
2058 static char buf[BUF_SIZE + 1];
2059 static int firstTime = TRUE, intfSet = FALSE;
2060 static ColorClass prevColor = ColorNormal;
2061 static int savingComment = FALSE;
2062 static int cmatch = 0; // continuation sequence match
2069 int backup; /* [DM] For zippy color lines */
2071 char talker[MSG_SIZ]; // [HGM] chat
2074 if (appData.debugMode) {
2076 fprintf(debugFP, "<ICS: ");
2077 show_bytes(debugFP, data, count);
2078 fprintf(debugFP, "\n");
2082 if (appData.debugMode) { int f = forwardMostMove;
2083 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2084 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2087 /* If last read ended with a partial line that we couldn't parse,
2088 prepend it to the new read and try again. */
2089 if (leftover_len > 0) {
2090 for (i=0; i<leftover_len; i++)
2091 buf[i] = buf[leftover_start + i];
2094 /* copy new characters into the buffer */
2095 bp = buf + leftover_len;
2096 buf_len=leftover_len;
2097 for (i=0; i<count; i++)
2100 if (data[i] == '\r')
2103 // join lines split by ICS?
2104 if (!appData.noJoin)
2107 Joining just consists of finding matches against the
2108 continuation sequence, and discarding that sequence
2109 if found instead of copying it. So, until a match
2110 fails, there's nothing to do since it might be the
2111 complete sequence, and thus, something we don't want
2114 if (data[i] == cont_seq[cmatch])
2117 if (cmatch == strlen(cont_seq))
2119 cmatch = 0; // complete match. just reset the counter
2122 it's possible for the ICS to not include the space
2123 at the end of the last word, making our [correct]
2124 join operation fuse two separate words. the server
2125 does this when the space occurs at the width setting.
2127 if (!buf_len || buf[buf_len-1] != ' ')
2138 match failed, so we have to copy what matched before
2139 falling through and copying this character. In reality,
2140 this will only ever be just the newline character, but
2141 it doesn't hurt to be precise.
2143 strncpy(bp, cont_seq, cmatch);
2155 buf[buf_len] = NULLCHAR;
2156 next_out = leftover_len;
2160 while (i < buf_len) {
2161 /* Deal with part of the TELNET option negotiation
2162 protocol. We refuse to do anything beyond the
2163 defaults, except that we allow the WILL ECHO option,
2164 which ICS uses to turn off password echoing when we are
2165 directly connected to it. We reject this option
2166 if localLineEditing mode is on (always on in xboard)
2167 and we are talking to port 23, which might be a real
2168 telnet server that will try to keep WILL ECHO on permanently.
2170 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2171 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2172 unsigned char option;
2174 switch ((unsigned char) buf[++i]) {
2176 if (appData.debugMode)
2177 fprintf(debugFP, "\n<WILL ");
2178 switch (option = (unsigned char) buf[++i]) {
2180 if (appData.debugMode)
2181 fprintf(debugFP, "ECHO ");
2182 /* Reply only if this is a change, according
2183 to the protocol rules. */
2184 if (remoteEchoOption) break;
2185 if (appData.localLineEditing &&
2186 atoi(appData.icsPort) == TN_PORT) {
2187 TelnetRequest(TN_DONT, TN_ECHO);
2190 TelnetRequest(TN_DO, TN_ECHO);
2191 remoteEchoOption = TRUE;
2195 if (appData.debugMode)
2196 fprintf(debugFP, "%d ", option);
2197 /* Whatever this is, we don't want it. */
2198 TelnetRequest(TN_DONT, option);
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<WONT ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 if (appData.debugMode)
2208 fprintf(debugFP, "ECHO ");
2209 /* Reply only if this is a change, according
2210 to the protocol rules. */
2211 if (!remoteEchoOption) break;
2213 TelnetRequest(TN_DONT, TN_ECHO);
2214 remoteEchoOption = FALSE;
2217 if (appData.debugMode)
2218 fprintf(debugFP, "%d ", (unsigned char) option);
2219 /* Whatever this is, it must already be turned
2220 off, because we never agree to turn on
2221 anything non-default, so according to the
2222 protocol rules, we don't reply. */
2227 if (appData.debugMode)
2228 fprintf(debugFP, "\n<DO ");
2229 switch (option = (unsigned char) buf[++i]) {
2231 /* Whatever this is, we refuse to do it. */
2232 if (appData.debugMode)
2233 fprintf(debugFP, "%d ", option);
2234 TelnetRequest(TN_WONT, option);
2239 if (appData.debugMode)
2240 fprintf(debugFP, "\n<DONT ");
2241 switch (option = (unsigned char) buf[++i]) {
2243 if (appData.debugMode)
2244 fprintf(debugFP, "%d ", option);
2245 /* Whatever this is, we are already not doing
2246 it, because we never agree to do anything
2247 non-default, so according to the protocol
2248 rules, we don't reply. */
2253 if (appData.debugMode)
2254 fprintf(debugFP, "\n<IAC ");
2255 /* Doubled IAC; pass it through */
2259 if (appData.debugMode)
2260 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2261 /* Drop all other telnet commands on the floor */
2264 if (oldi > next_out)
2265 SendToPlayer(&buf[next_out], oldi - next_out);
2271 /* OK, this at least will *usually* work */
2272 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2276 if (loggedOn && !intfSet) {
2277 if (ics_type == ICS_ICC) {
2279 "/set-quietly interface %s\n/set-quietly style 12\n",
2281 } else if (ics_type == ICS_CHESSNET) {
2282 sprintf(str, "/style 12\n");
2284 strcpy(str, "alias $ @\n$set interface ");
2285 strcat(str, programVersion);
2286 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2288 strcat(str, "$iset nohighlight 1\n");
2290 strcat(str, "$iset lock 1\n$style 12\n");
2293 NotifyFrontendLogin();
2297 if (started == STARTED_COMMENT) {
2298 /* Accumulate characters in comment */
2299 parse[parse_pos++] = buf[i];
2300 if (buf[i] == '\n') {
2301 parse[parse_pos] = NULLCHAR;
2302 if(chattingPartner>=0) {
2304 sprintf(mess, "%s%s", talker, parse);
2305 OutputChatMessage(chattingPartner, mess);
2306 chattingPartner = -1;
2308 if(!suppressKibitz) // [HGM] kibitz
2309 AppendComment(forwardMostMove, StripHighlight(parse));
2310 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2311 int nrDigit = 0, nrAlph = 0, i;
2312 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2313 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2314 parse[parse_pos] = NULLCHAR;
2315 // try to be smart: if it does not look like search info, it should go to
2316 // ICS interaction window after all, not to engine-output window.
2317 for(i=0; i<parse_pos; i++) { // count letters and digits
2318 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2319 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2320 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2322 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2323 int depth=0; float score;
2324 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2325 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2326 pvInfoList[forwardMostMove-1].depth = depth;
2327 pvInfoList[forwardMostMove-1].score = 100*score;
2329 OutputKibitz(suppressKibitz, parse);
2332 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2333 SendToPlayer(tmp, strlen(tmp));
2336 started = STARTED_NONE;
2338 /* Don't match patterns against characters in chatter */
2343 if (started == STARTED_CHATTER) {
2344 if (buf[i] != '\n') {
2345 /* Don't match patterns against characters in chatter */
2349 started = STARTED_NONE;
2352 /* Kludge to deal with rcmd protocol */
2353 if (firstTime && looking_at(buf, &i, "\001*")) {
2354 DisplayFatalError(&buf[1], 0, 1);
2360 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2363 if (appData.debugMode)
2364 fprintf(debugFP, "ics_type %d\n", ics_type);
2367 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2368 ics_type = ICS_FICS;
2370 if (appData.debugMode)
2371 fprintf(debugFP, "ics_type %d\n", ics_type);
2374 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2375 ics_type = ICS_CHESSNET;
2377 if (appData.debugMode)
2378 fprintf(debugFP, "ics_type %d\n", ics_type);
2383 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2384 looking_at(buf, &i, "Logging you in as \"*\"") ||
2385 looking_at(buf, &i, "will be \"*\""))) {
2386 strcpy(ics_handle, star_match[0]);
2390 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2392 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2393 DisplayIcsInteractionTitle(buf);
2394 have_set_title = TRUE;
2397 /* skip finger notes */
2398 if (started == STARTED_NONE &&
2399 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2400 (buf[i] == '1' && buf[i+1] == '0')) &&
2401 buf[i+2] == ':' && buf[i+3] == ' ') {
2402 started = STARTED_CHATTER;
2407 /* skip formula vars */
2408 if (started == STARTED_NONE &&
2409 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2410 started = STARTED_CHATTER;
2416 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2417 if (appData.autoKibitz && started == STARTED_NONE &&
2418 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2419 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2420 if(looking_at(buf, &i, "* kibitzes: ") &&
2421 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2422 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2423 suppressKibitz = TRUE;
2424 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2425 && (gameMode == IcsPlayingWhite)) ||
2426 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2427 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2428 started = STARTED_CHATTER; // own kibitz we simply discard
2430 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2431 parse_pos = 0; parse[0] = NULLCHAR;
2432 savingComment = TRUE;
2433 suppressKibitz = gameMode != IcsObserving ? 2 :
2434 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2438 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2439 started = STARTED_CHATTER;
2440 suppressKibitz = TRUE;
2442 } // [HGM] kibitz: end of patch
2444 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2446 // [HGM] chat: intercept tells by users for which we have an open chat window
2448 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2449 looking_at(buf, &i, "* whispers:") ||
2450 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2451 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2453 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2454 chattingPartner = -1;
2456 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2457 for(p=0; p<MAX_CHAT; p++) {
2458 if(channel == atoi(chatPartner[p])) {
2459 talker[0] = '['; strcat(talker, "]");
2460 chattingPartner = p; break;
2463 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2464 for(p=0; p<MAX_CHAT; p++) {
2465 if(!strcmp("WHISPER", chatPartner[p])) {
2466 talker[0] = '['; strcat(talker, "]");
2467 chattingPartner = p; break;
2470 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2471 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2473 chattingPartner = p; break;
2475 if(chattingPartner<0) i = oldi; else {
2476 started = STARTED_COMMENT;
2477 parse_pos = 0; parse[0] = NULLCHAR;
2478 savingComment = TRUE;
2479 suppressKibitz = TRUE;
2481 } // [HGM] chat: end of patch
2483 if (appData.zippyTalk || appData.zippyPlay) {
2484 /* [DM] Backup address for color zippy lines */
2488 if (loggedOn == TRUE)
2489 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2490 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2492 if (ZippyControl(buf, &i) ||
2493 ZippyConverse(buf, &i) ||
2494 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2496 if (!appData.colorize) continue;
2500 } // [DM] 'else { ' deleted
2502 /* Regular tells and says */
2503 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2504 looking_at(buf, &i, "* (your partner) tells you: ") ||
2505 looking_at(buf, &i, "* says: ") ||
2506 /* Don't color "message" or "messages" output */
2507 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2508 looking_at(buf, &i, "*. * at *:*: ") ||
2509 looking_at(buf, &i, "--* (*:*): ") ||
2510 /* Message notifications (same color as tells) */
2511 looking_at(buf, &i, "* has left a message ") ||
2512 looking_at(buf, &i, "* just sent you a message:\n") ||
2513 /* Whispers and kibitzes */
2514 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2515 looking_at(buf, &i, "* kibitzes: ") ||
2517 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2519 if (tkind == 1 && strchr(star_match[0], ':')) {
2520 /* Avoid "tells you:" spoofs in channels */
2523 if (star_match[0][0] == NULLCHAR ||
2524 strchr(star_match[0], ' ') ||
2525 (tkind == 3 && strchr(star_match[1], ' '))) {
2526 /* Reject bogus matches */
2529 if (appData.colorize) {
2530 if (oldi > next_out) {
2531 SendToPlayer(&buf[next_out], oldi - next_out);
2536 Colorize(ColorTell, FALSE);
2537 curColor = ColorTell;
2540 Colorize(ColorKibitz, FALSE);
2541 curColor = ColorKibitz;
2544 p = strrchr(star_match[1], '(');
2551 Colorize(ColorChannel1, FALSE);
2552 curColor = ColorChannel1;
2554 Colorize(ColorChannel, FALSE);
2555 curColor = ColorChannel;
2559 curColor = ColorNormal;
2563 if (started == STARTED_NONE && appData.autoComment &&
2564 (gameMode == IcsObserving ||
2565 gameMode == IcsPlayingWhite ||
2566 gameMode == IcsPlayingBlack)) {
2567 parse_pos = i - oldi;
2568 memcpy(parse, &buf[oldi], parse_pos);
2569 parse[parse_pos] = NULLCHAR;
2570 started = STARTED_COMMENT;
2571 savingComment = TRUE;
2573 started = STARTED_CHATTER;
2574 savingComment = FALSE;
2581 if (looking_at(buf, &i, "* s-shouts: ") ||
2582 looking_at(buf, &i, "* c-shouts: ")) {
2583 if (appData.colorize) {
2584 if (oldi > next_out) {
2585 SendToPlayer(&buf[next_out], oldi - next_out);
2588 Colorize(ColorSShout, FALSE);
2589 curColor = ColorSShout;
2592 started = STARTED_CHATTER;
2596 if (looking_at(buf, &i, "--->")) {
2601 if (looking_at(buf, &i, "* shouts: ") ||
2602 looking_at(buf, &i, "--> ")) {
2603 if (appData.colorize) {
2604 if (oldi > next_out) {
2605 SendToPlayer(&buf[next_out], oldi - next_out);
2608 Colorize(ColorShout, FALSE);
2609 curColor = ColorShout;
2612 started = STARTED_CHATTER;
2616 if (looking_at( buf, &i, "Challenge:")) {
2617 if (appData.colorize) {
2618 if (oldi > next_out) {
2619 SendToPlayer(&buf[next_out], oldi - next_out);
2622 Colorize(ColorChallenge, FALSE);
2623 curColor = ColorChallenge;
2629 if (looking_at(buf, &i, "* offers you") ||
2630 looking_at(buf, &i, "* offers to be") ||
2631 looking_at(buf, &i, "* would like to") ||
2632 looking_at(buf, &i, "* requests to") ||
2633 looking_at(buf, &i, "Your opponent offers") ||
2634 looking_at(buf, &i, "Your opponent requests")) {
2636 if (appData.colorize) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(ColorRequest, FALSE);
2642 curColor = ColorRequest;
2647 if (looking_at(buf, &i, "* (*) seeking")) {
2648 if (appData.colorize) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 Colorize(ColorSeek, FALSE);
2654 curColor = ColorSeek;
2659 if (looking_at(buf, &i, "\\ ")) {
2660 if (prevColor != ColorNormal) {
2661 if (oldi > next_out) {
2662 SendToPlayer(&buf[next_out], oldi - next_out);
2665 Colorize(prevColor, TRUE);
2666 curColor = prevColor;
2668 if (savingComment) {
2669 parse_pos = i - oldi;
2670 memcpy(parse, &buf[oldi], parse_pos);
2671 parse[parse_pos] = NULLCHAR;
2672 started = STARTED_COMMENT;
2674 started = STARTED_CHATTER;
2679 if (looking_at(buf, &i, "Black Strength :") ||
2680 looking_at(buf, &i, "<<< style 10 board >>>") ||
2681 looking_at(buf, &i, "<10>") ||
2682 looking_at(buf, &i, "#@#")) {
2683 /* Wrong board style */
2685 SendToICS(ics_prefix);
2686 SendToICS("set style 12\n");
2687 SendToICS(ics_prefix);
2688 SendToICS("refresh\n");
2692 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2694 have_sent_ICS_logon = 1;
2698 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2699 (looking_at(buf, &i, "\n<12> ") ||
2700 looking_at(buf, &i, "<12> "))) {
2702 if (oldi > next_out) {
2703 SendToPlayer(&buf[next_out], oldi - next_out);
2706 started = STARTED_BOARD;
2711 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2712 looking_at(buf, &i, "<b1> ")) {
2713 if (oldi > next_out) {
2714 SendToPlayer(&buf[next_out], oldi - next_out);
2717 started = STARTED_HOLDINGS;
2722 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2724 /* Header for a move list -- first line */
2726 switch (ics_getting_history) {
2730 case BeginningOfGame:
2731 /* User typed "moves" or "oldmoves" while we
2732 were idle. Pretend we asked for these
2733 moves and soak them up so user can step
2734 through them and/or save them.
2737 gameMode = IcsObserving;
2740 ics_getting_history = H_GOT_UNREQ_HEADER;
2742 case EditGame: /*?*/
2743 case EditPosition: /*?*/
2744 /* Should above feature work in these modes too? */
2745 /* For now it doesn't */
2746 ics_getting_history = H_GOT_UNWANTED_HEADER;
2749 ics_getting_history = H_GOT_UNWANTED_HEADER;
2754 /* Is this the right one? */
2755 if (gameInfo.white && gameInfo.black &&
2756 strcmp(gameInfo.white, star_match[0]) == 0 &&
2757 strcmp(gameInfo.black, star_match[2]) == 0) {
2759 ics_getting_history = H_GOT_REQ_HEADER;
2762 case H_GOT_REQ_HEADER:
2763 case H_GOT_UNREQ_HEADER:
2764 case H_GOT_UNWANTED_HEADER:
2765 case H_GETTING_MOVES:
2766 /* Should not happen */
2767 DisplayError(_("Error gathering move list: two headers"), 0);
2768 ics_getting_history = H_FALSE;
2772 /* Save player ratings into gameInfo if needed */
2773 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2774 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2775 (gameInfo.whiteRating == -1 ||
2776 gameInfo.blackRating == -1)) {
2778 gameInfo.whiteRating = string_to_rating(star_match[1]);
2779 gameInfo.blackRating = string_to_rating(star_match[3]);
2780 if (appData.debugMode)
2781 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2782 gameInfo.whiteRating, gameInfo.blackRating);
2787 if (looking_at(buf, &i,
2788 "* * match, initial time: * minute*, increment: * second")) {
2789 /* Header for a move list -- second line */
2790 /* Initial board will follow if this is a wild game */
2791 if (gameInfo.event != NULL) free(gameInfo.event);
2792 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2793 gameInfo.event = StrSave(str);
2794 /* [HGM] we switched variant. Translate boards if needed. */
2795 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2799 if (looking_at(buf, &i, "Move ")) {
2800 /* Beginning of a move list */
2801 switch (ics_getting_history) {
2803 /* Normally should not happen */
2804 /* Maybe user hit reset while we were parsing */
2807 /* Happens if we are ignoring a move list that is not
2808 * the one we just requested. Common if the user
2809 * tries to observe two games without turning off
2812 case H_GETTING_MOVES:
2813 /* Should not happen */
2814 DisplayError(_("Error gathering move list: nested"), 0);
2815 ics_getting_history = H_FALSE;
2817 case H_GOT_REQ_HEADER:
2818 ics_getting_history = H_GETTING_MOVES;
2819 started = STARTED_MOVES;
2821 if (oldi > next_out) {
2822 SendToPlayer(&buf[next_out], oldi - next_out);
2825 case H_GOT_UNREQ_HEADER:
2826 ics_getting_history = H_GETTING_MOVES;
2827 started = STARTED_MOVES_NOHIDE;
2830 case H_GOT_UNWANTED_HEADER:
2831 ics_getting_history = H_FALSE;
2837 if (looking_at(buf, &i, "% ") ||
2838 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2839 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2840 savingComment = FALSE;
2843 case STARTED_MOVES_NOHIDE:
2844 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2845 parse[parse_pos + i - oldi] = NULLCHAR;
2846 ParseGameHistory(parse);
2848 if (appData.zippyPlay && first.initDone) {
2849 FeedMovesToProgram(&first, forwardMostMove);
2850 if (gameMode == IcsPlayingWhite) {
2851 if (WhiteOnMove(forwardMostMove)) {
2852 if (first.sendTime) {
2853 if (first.useColors) {
2854 SendToProgram("black\n", &first);
2856 SendTimeRemaining(&first, TRUE);
2858 if (first.useColors) {
2859 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2861 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2862 first.maybeThinking = TRUE;
2864 if (first.usePlayother) {
2865 if (first.sendTime) {
2866 SendTimeRemaining(&first, TRUE);
2868 SendToProgram("playother\n", &first);
2874 } else if (gameMode == IcsPlayingBlack) {
2875 if (!WhiteOnMove(forwardMostMove)) {
2876 if (first.sendTime) {
2877 if (first.useColors) {
2878 SendToProgram("white\n", &first);
2880 SendTimeRemaining(&first, FALSE);
2882 if (first.useColors) {
2883 SendToProgram("black\n", &first);
2885 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2886 first.maybeThinking = TRUE;
2888 if (first.usePlayother) {
2889 if (first.sendTime) {
2890 SendTimeRemaining(&first, FALSE);
2892 SendToProgram("playother\n", &first);
2901 if (gameMode == IcsObserving && ics_gamenum == -1) {
2902 /* Moves came from oldmoves or moves command
2903 while we weren't doing anything else.
2905 currentMove = forwardMostMove;
2906 ClearHighlights();/*!!could figure this out*/
2907 flipView = appData.flipView;
2908 DrawPosition(TRUE, boards[currentMove]);
2909 DisplayBothClocks();
2910 sprintf(str, "%s vs. %s",
2911 gameInfo.white, gameInfo.black);
2915 /* Moves were history of an active game */
2916 if (gameInfo.resultDetails != NULL) {
2917 free(gameInfo.resultDetails);
2918 gameInfo.resultDetails = NULL;
2921 HistorySet(parseList, backwardMostMove,
2922 forwardMostMove, currentMove-1);
2923 DisplayMove(currentMove - 1);
2924 if (started == STARTED_MOVES) next_out = i;
2925 started = STARTED_NONE;
2926 ics_getting_history = H_FALSE;
2929 case STARTED_OBSERVE:
2930 started = STARTED_NONE;
2931 SendToICS(ics_prefix);
2932 SendToICS("refresh\n");
2938 if(bookHit) { // [HGM] book: simulate book reply
2939 static char bookMove[MSG_SIZ]; // a bit generous?
2941 programStats.nodes = programStats.depth = programStats.time =
2942 programStats.score = programStats.got_only_move = 0;
2943 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2945 strcpy(bookMove, "move ");
2946 strcat(bookMove, bookHit);
2947 HandleMachineMove(bookMove, &first);
2952 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2953 started == STARTED_HOLDINGS ||
2954 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2955 /* Accumulate characters in move list or board */
2956 parse[parse_pos++] = buf[i];
2959 /* Start of game messages. Mostly we detect start of game
2960 when the first board image arrives. On some versions
2961 of the ICS, though, we need to do a "refresh" after starting
2962 to observe in order to get the current board right away. */
2963 if (looking_at(buf, &i, "Adding game * to observation list")) {
2964 started = STARTED_OBSERVE;
2968 /* Handle auto-observe */
2969 if (appData.autoObserve &&
2970 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2971 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2973 /* Choose the player that was highlighted, if any. */
2974 if (star_match[0][0] == '\033' ||
2975 star_match[1][0] != '\033') {
2976 player = star_match[0];
2978 player = star_match[2];
2980 sprintf(str, "%sobserve %s\n",
2981 ics_prefix, StripHighlightAndTitle(player));
2984 /* Save ratings from notify string */
2985 strcpy(player1Name, star_match[0]);
2986 player1Rating = string_to_rating(star_match[1]);
2987 strcpy(player2Name, star_match[2]);
2988 player2Rating = string_to_rating(star_match[3]);
2990 if (appData.debugMode)
2992 "Ratings from 'Game notification:' %s %d, %s %d\n",
2993 player1Name, player1Rating,
2994 player2Name, player2Rating);
2999 /* Deal with automatic examine mode after a game,
3000 and with IcsObserving -> IcsExamining transition */
3001 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3002 looking_at(buf, &i, "has made you an examiner of game *")) {
3004 int gamenum = atoi(star_match[0]);
3005 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3006 gamenum == ics_gamenum) {
3007 /* We were already playing or observing this game;
3008 no need to refetch history */
3009 gameMode = IcsExamining;
3011 pauseExamForwardMostMove = forwardMostMove;
3012 } else if (currentMove < forwardMostMove) {
3013 ForwardInner(forwardMostMove);
3016 /* I don't think this case really can happen */
3017 SendToICS(ics_prefix);
3018 SendToICS("refresh\n");
3023 /* Error messages */
3024 // if (ics_user_moved) {
3025 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3026 if (looking_at(buf, &i, "Illegal move") ||
3027 looking_at(buf, &i, "Not a legal move") ||
3028 looking_at(buf, &i, "Your king is in check") ||
3029 looking_at(buf, &i, "It isn't your turn") ||
3030 looking_at(buf, &i, "It is not your move")) {
3032 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3033 currentMove = --forwardMostMove;
3034 DisplayMove(currentMove - 1); /* before DMError */
3035 DrawPosition(FALSE, boards[currentMove]);
3037 DisplayBothClocks();
3039 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3045 if (looking_at(buf, &i, "still have time") ||
3046 looking_at(buf, &i, "not out of time") ||
3047 looking_at(buf, &i, "either player is out of time") ||
3048 looking_at(buf, &i, "has timeseal; checking")) {
3049 /* We must have called his flag a little too soon */
3050 whiteFlag = blackFlag = FALSE;
3054 if (looking_at(buf, &i, "added * seconds to") ||
3055 looking_at(buf, &i, "seconds were added to")) {
3056 /* Update the clocks */
3057 SendToICS(ics_prefix);
3058 SendToICS("refresh\n");
3062 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3063 ics_clock_paused = TRUE;
3068 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3069 ics_clock_paused = FALSE;
3074 /* Grab player ratings from the Creating: message.
3075 Note we have to check for the special case when
3076 the ICS inserts things like [white] or [black]. */
3077 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3078 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3080 0 player 1 name (not necessarily white)
3082 2 empty, white, or black (IGNORED)
3083 3 player 2 name (not necessarily black)
3086 The names/ratings are sorted out when the game
3087 actually starts (below).
3089 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3090 player1Rating = string_to_rating(star_match[1]);
3091 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3092 player2Rating = string_to_rating(star_match[4]);
3094 if (appData.debugMode)
3096 "Ratings from 'Creating:' %s %d, %s %d\n",
3097 player1Name, player1Rating,
3098 player2Name, player2Rating);
3103 /* Improved generic start/end-of-game messages */
3104 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3105 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3106 /* If tkind == 0: */
3107 /* star_match[0] is the game number */
3108 /* [1] is the white player's name */
3109 /* [2] is the black player's name */
3110 /* For end-of-game: */
3111 /* [3] is the reason for the game end */
3112 /* [4] is a PGN end game-token, preceded by " " */
3113 /* For start-of-game: */
3114 /* [3] begins with "Creating" or "Continuing" */
3115 /* [4] is " *" or empty (don't care). */
3116 int gamenum = atoi(star_match[0]);
3117 char *whitename, *blackname, *why, *endtoken;
3118 ChessMove endtype = (ChessMove) 0;
3121 whitename = star_match[1];
3122 blackname = star_match[2];
3123 why = star_match[3];
3124 endtoken = star_match[4];
3126 whitename = star_match[1];
3127 blackname = star_match[3];
3128 why = star_match[5];
3129 endtoken = star_match[6];
3132 /* Game start messages */
3133 if (strncmp(why, "Creating ", 9) == 0 ||
3134 strncmp(why, "Continuing ", 11) == 0) {
3135 gs_gamenum = gamenum;
3136 strcpy(gs_kind, strchr(why, ' ') + 1);
3138 if (appData.zippyPlay) {
3139 ZippyGameStart(whitename, blackname);
3145 /* Game end messages */
3146 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3147 ics_gamenum != gamenum) {
3150 while (endtoken[0] == ' ') endtoken++;
3151 switch (endtoken[0]) {
3154 endtype = GameUnfinished;
3157 endtype = BlackWins;
3160 if (endtoken[1] == '/')
3161 endtype = GameIsDrawn;
3163 endtype = WhiteWins;
3166 GameEnds(endtype, why, GE_ICS);
3168 if (appData.zippyPlay && first.initDone) {
3169 ZippyGameEnd(endtype, why);
3170 if (first.pr == NULL) {
3171 /* Start the next process early so that we'll
3172 be ready for the next challenge */
3173 StartChessProgram(&first);
3175 /* Send "new" early, in case this command takes
3176 a long time to finish, so that we'll be ready
3177 for the next challenge. */
3178 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3185 if (looking_at(buf, &i, "Removing game * from observation") ||
3186 looking_at(buf, &i, "no longer observing game *") ||
3187 looking_at(buf, &i, "Game * (*) has no examiners")) {
3188 if (gameMode == IcsObserving &&
3189 atoi(star_match[0]) == ics_gamenum)
3191 /* icsEngineAnalyze */
3192 if (appData.icsEngineAnalyze) {
3199 ics_user_moved = FALSE;
3204 if (looking_at(buf, &i, "no longer examining game *")) {
3205 if (gameMode == IcsExamining &&
3206 atoi(star_match[0]) == ics_gamenum)
3210 ics_user_moved = FALSE;
3215 /* Advance leftover_start past any newlines we find,
3216 so only partial lines can get reparsed */
3217 if (looking_at(buf, &i, "\n")) {
3218 prevColor = curColor;
3219 if (curColor != ColorNormal) {
3220 if (oldi > next_out) {
3221 SendToPlayer(&buf[next_out], oldi - next_out);
3224 Colorize(ColorNormal, FALSE);
3225 curColor = ColorNormal;
3227 if (started == STARTED_BOARD) {
3228 started = STARTED_NONE;
3229 parse[parse_pos] = NULLCHAR;
3230 ParseBoard12(parse);
3233 /* Send premove here */
3234 if (appData.premove) {
3236 if (currentMove == 0 &&
3237 gameMode == IcsPlayingWhite &&
3238 appData.premoveWhite) {
3239 sprintf(str, "%s\n", appData.premoveWhiteText);
3240 if (appData.debugMode)
3241 fprintf(debugFP, "Sending premove:\n");
3243 } else if (currentMove == 1 &&
3244 gameMode == IcsPlayingBlack &&
3245 appData.premoveBlack) {
3246 sprintf(str, "%s\n", appData.premoveBlackText);
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3250 } else if (gotPremove) {
3252 ClearPremoveHighlights();
3253 if (appData.debugMode)
3254 fprintf(debugFP, "Sending premove:\n");
3255 UserMoveEvent(premoveFromX, premoveFromY,
3256 premoveToX, premoveToY,
3261 /* Usually suppress following prompt */
3262 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3263 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3264 if (looking_at(buf, &i, "*% ")) {
3265 savingComment = FALSE;
3269 } else if (started == STARTED_HOLDINGS) {
3271 char new_piece[MSG_SIZ];
3272 started = STARTED_NONE;
3273 parse[parse_pos] = NULLCHAR;
3274 if (appData.debugMode)
3275 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3276 parse, currentMove);
3277 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3278 gamenum == ics_gamenum) {
3279 if (gameInfo.variant == VariantNormal) {
3280 /* [HGM] We seem to switch variant during a game!
3281 * Presumably no holdings were displayed, so we have
3282 * to move the position two files to the right to
3283 * create room for them!
3285 VariantClass newVariant;
3286 switch(gameInfo.boardWidth) { // base guess on board width
3287 case 9: newVariant = VariantShogi; break;
3288 case 10: newVariant = VariantGreat; break;
3289 default: newVariant = VariantCrazyhouse; break;
3291 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3292 /* Get a move list just to see the header, which
3293 will tell us whether this is really bug or zh */
3294 if (ics_getting_history == H_FALSE) {
3295 ics_getting_history = H_REQUESTED;
3296 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3300 new_piece[0] = NULLCHAR;
3301 sscanf(parse, "game %d white [%s black [%s <- %s",
3302 &gamenum, white_holding, black_holding,
3304 white_holding[strlen(white_holding)-1] = NULLCHAR;
3305 black_holding[strlen(black_holding)-1] = NULLCHAR;
3306 /* [HGM] copy holdings to board holdings area */
3307 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3308 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3309 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3311 if (appData.zippyPlay && first.initDone) {
3312 ZippyHoldings(white_holding, black_holding,
3316 if (tinyLayout || smallLayout) {
3317 char wh[16], bh[16];
3318 PackHolding(wh, white_holding);
3319 PackHolding(bh, black_holding);
3320 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3321 gameInfo.white, gameInfo.black);
3323 sprintf(str, "%s [%s] vs. %s [%s]",
3324 gameInfo.white, white_holding,
3325 gameInfo.black, black_holding);
3328 DrawPosition(FALSE, boards[currentMove]);
3331 /* Suppress following prompt */
3332 if (looking_at(buf, &i, "*% ")) {
3333 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3334 savingComment = FALSE;
3341 i++; /* skip unparsed character and loop back */
3344 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3345 started != STARTED_HOLDINGS && i > next_out) {
3346 SendToPlayer(&buf[next_out], i - next_out);
3349 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3351 leftover_len = buf_len - leftover_start;
3352 /* if buffer ends with something we couldn't parse,
3353 reparse it after appending the next read */
3355 } else if (count == 0) {
3356 RemoveInputSource(isr);
3357 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3359 DisplayFatalError(_("Error reading from ICS"), error, 1);
3364 /* Board style 12 looks like this:
3366 <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
3368 * The "<12> " is stripped before it gets to this routine. The two
3369 * trailing 0's (flip state and clock ticking) are later addition, and
3370 * some chess servers may not have them, or may have only the first.
3371 * Additional trailing fields may be added in the future.
3374 #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"
3376 #define RELATION_OBSERVING_PLAYED 0
3377 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3378 #define RELATION_PLAYING_MYMOVE 1
3379 #define RELATION_PLAYING_NOTMYMOVE -1
3380 #define RELATION_EXAMINING 2
3381 #define RELATION_ISOLATED_BOARD -3
3382 #define RELATION_STARTING_POSITION -4 /* FICS only */
3385 ParseBoard12(string)
3388 GameMode newGameMode;
3389 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3390 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3391 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3392 char to_play, board_chars[200];
3393 char move_str[500], str[500], elapsed_time[500];
3394 char black[32], white[32];
3396 int prevMove = currentMove;
3399 int fromX, fromY, toX, toY;
3401 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3402 char *bookHit = NULL; // [HGM] book
3403 Boolean weird = FALSE, reqFlag = FALSE;
3405 fromX = fromY = toX = toY = -1;
3409 if (appData.debugMode)
3410 fprintf(debugFP, _("Parsing board: %s\n"), string);
3412 move_str[0] = NULLCHAR;
3413 elapsed_time[0] = NULLCHAR;
3414 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3416 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3417 if(string[i] == ' ') { ranks++; files = 0; }
3419 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3422 for(j = 0; j <i; j++) board_chars[j] = string[j];
3423 board_chars[i] = '\0';
3426 n = sscanf(string, PATTERN, &to_play, &double_push,
3427 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3428 &gamenum, white, black, &relation, &basetime, &increment,
3429 &white_stren, &black_stren, &white_time, &black_time,
3430 &moveNum, str, elapsed_time, move_str, &ics_flip,
3434 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3435 DisplayError(str, 0);
3439 /* Convert the move number to internal form */
3440 moveNum = (moveNum - 1) * 2;
3441 if (to_play == 'B') moveNum++;
3442 if (moveNum >= MAX_MOVES) {
3443 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3449 case RELATION_OBSERVING_PLAYED:
3450 case RELATION_OBSERVING_STATIC:
3451 if (gamenum == -1) {
3452 /* Old ICC buglet */
3453 relation = RELATION_OBSERVING_STATIC;
3455 newGameMode = IcsObserving;
3457 case RELATION_PLAYING_MYMOVE:
3458 case RELATION_PLAYING_NOTMYMOVE:
3460 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3461 IcsPlayingWhite : IcsPlayingBlack;
3463 case RELATION_EXAMINING:
3464 newGameMode = IcsExamining;
3466 case RELATION_ISOLATED_BOARD:
3468 /* Just display this board. If user was doing something else,
3469 we will forget about it until the next board comes. */
3470 newGameMode = IcsIdle;
3472 case RELATION_STARTING_POSITION:
3473 newGameMode = gameMode;
3477 /* Modify behavior for initial board display on move listing
3480 switch (ics_getting_history) {
3484 case H_GOT_REQ_HEADER:
3485 case H_GOT_UNREQ_HEADER:
3486 /* This is the initial position of the current game */
3487 gamenum = ics_gamenum;
3488 moveNum = 0; /* old ICS bug workaround */
3489 if (to_play == 'B') {
3490 startedFromSetupPosition = TRUE;
3491 blackPlaysFirst = TRUE;
3493 if (forwardMostMove == 0) forwardMostMove = 1;
3494 if (backwardMostMove == 0) backwardMostMove = 1;
3495 if (currentMove == 0) currentMove = 1;
3497 newGameMode = gameMode;
3498 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3500 case H_GOT_UNWANTED_HEADER:
3501 /* This is an initial board that we don't want */
3503 case H_GETTING_MOVES:
3504 /* Should not happen */
3505 DisplayError(_("Error gathering move list: extra board"), 0);
3506 ics_getting_history = H_FALSE;
3510 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3511 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3512 /* [HGM] We seem to have switched variant unexpectedly
3513 * Try to guess new variant from board size
3515 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3516 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3517 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3518 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3519 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3520 if(!weird) newVariant = VariantNormal;
3521 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3522 /* Get a move list just to see the header, which
3523 will tell us whether this is really bug or zh */
3524 if (ics_getting_history == H_FALSE) {
3525 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3526 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3531 /* Take action if this is the first board of a new game, or of a
3532 different game than is currently being displayed. */
3533 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3534 relation == RELATION_ISOLATED_BOARD) {
3536 /* Forget the old game and get the history (if any) of the new one */
3537 if (gameMode != BeginningOfGame) {
3541 if (appData.autoRaiseBoard) BoardToTop();
3543 if (gamenum == -1) {
3544 newGameMode = IcsIdle;
3545 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3546 appData.getMoveList && !reqFlag) {
3547 /* Need to get game history */
3548 ics_getting_history = H_REQUESTED;
3549 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3553 /* Initially flip the board to have black on the bottom if playing
3554 black or if the ICS flip flag is set, but let the user change
3555 it with the Flip View button. */
3556 flipView = appData.autoFlipView ?
3557 (newGameMode == IcsPlayingBlack) || ics_flip :
3560 /* Done with values from previous mode; copy in new ones */
3561 gameMode = newGameMode;
3563 ics_gamenum = gamenum;
3564 if (gamenum == gs_gamenum) {
3565 int klen = strlen(gs_kind);
3566 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3567 sprintf(str, "ICS %s", gs_kind);
3568 gameInfo.event = StrSave(str);
3570 gameInfo.event = StrSave("ICS game");
3572 gameInfo.site = StrSave(appData.icsHost);
3573 gameInfo.date = PGNDate();
3574 gameInfo.round = StrSave("-");
3575 gameInfo.white = StrSave(white);
3576 gameInfo.black = StrSave(black);
3577 timeControl = basetime * 60 * 1000;
3579 timeIncrement = increment * 1000;
3580 movesPerSession = 0;
3581 gameInfo.timeControl = TimeControlTagValue();
3582 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3583 if (appData.debugMode) {
3584 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3585 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3586 setbuf(debugFP, NULL);
3589 gameInfo.outOfBook = NULL;
3591 /* Do we have the ratings? */
3592 if (strcmp(player1Name, white) == 0 &&
3593 strcmp(player2Name, black) == 0) {
3594 if (appData.debugMode)
3595 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3596 player1Rating, player2Rating);
3597 gameInfo.whiteRating = player1Rating;
3598 gameInfo.blackRating = player2Rating;
3599 } else if (strcmp(player2Name, white) == 0 &&
3600 strcmp(player1Name, black) == 0) {
3601 if (appData.debugMode)
3602 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3603 player2Rating, player1Rating);
3604 gameInfo.whiteRating = player2Rating;
3605 gameInfo.blackRating = player1Rating;
3607 player1Name[0] = player2Name[0] = NULLCHAR;
3609 /* Silence shouts if requested */
3610 if (appData.quietPlay &&
3611 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3612 SendToICS(ics_prefix);
3613 SendToICS("set shout 0\n");
3617 /* Deal with midgame name changes */
3619 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3620 if (gameInfo.white) free(gameInfo.white);
3621 gameInfo.white = StrSave(white);
3623 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3624 if (gameInfo.black) free(gameInfo.black);
3625 gameInfo.black = StrSave(black);
3629 /* Throw away game result if anything actually changes in examine mode */
3630 if (gameMode == IcsExamining && !newGame) {
3631 gameInfo.result = GameUnfinished;
3632 if (gameInfo.resultDetails != NULL) {
3633 free(gameInfo.resultDetails);
3634 gameInfo.resultDetails = NULL;
3638 /* In pausing && IcsExamining mode, we ignore boards coming
3639 in if they are in a different variation than we are. */
3640 if (pauseExamInvalid) return;
3641 if (pausing && gameMode == IcsExamining) {
3642 if (moveNum <= pauseExamForwardMostMove) {
3643 pauseExamInvalid = TRUE;
3644 forwardMostMove = pauseExamForwardMostMove;
3649 if (appData.debugMode) {
3650 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3652 /* Parse the board */
3653 for (k = 0; k < ranks; k++) {
3654 for (j = 0; j < files; j++)
3655 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3656 if(gameInfo.holdingsWidth > 1) {
3657 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3658 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3661 CopyBoard(boards[moveNum], board);
3662 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3664 startedFromSetupPosition =
3665 !CompareBoards(board, initialPosition);
3666 if(startedFromSetupPosition)
3667 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3670 /* [HGM] Set castling rights. Take the outermost Rooks,
3671 to make it also work for FRC opening positions. Note that board12
3672 is really defective for later FRC positions, as it has no way to
3673 indicate which Rook can castle if they are on the same side of King.
3674 For the initial position we grant rights to the outermost Rooks,
3675 and remember thos rights, and we then copy them on positions
3676 later in an FRC game. This means WB might not recognize castlings with
3677 Rooks that have moved back to their original position as illegal,
3678 but in ICS mode that is not its job anyway.
3680 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3681 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3683 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3684 if(board[0][i] == WhiteRook) j = i;
3685 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3686 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3687 if(board[0][i] == WhiteRook) j = i;
3688 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3689 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3690 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3691 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3692 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3693 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3694 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3696 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3697 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3698 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3699 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3700 if(board[BOARD_HEIGHT-1][k] == bKing)
3701 initialRights[5] = castlingRights[moveNum][5] = k;
3703 r = castlingRights[moveNum][0] = initialRights[0];
3704 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3705 r = castlingRights[moveNum][1] = initialRights[1];
3706 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3707 r = castlingRights[moveNum][3] = initialRights[3];
3708 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3709 r = castlingRights[moveNum][4] = initialRights[4];
3710 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3711 /* wildcastle kludge: always assume King has rights */
3712 r = castlingRights[moveNum][2] = initialRights[2];
3713 r = castlingRights[moveNum][5] = initialRights[5];
3715 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3716 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3719 if (ics_getting_history == H_GOT_REQ_HEADER ||
3720 ics_getting_history == H_GOT_UNREQ_HEADER) {
3721 /* This was an initial position from a move list, not
3722 the current position */
3726 /* Update currentMove and known move number limits */
3727 newMove = newGame || moveNum > forwardMostMove;
3730 forwardMostMove = backwardMostMove = currentMove = moveNum;
3731 if (gameMode == IcsExamining && moveNum == 0) {
3732 /* Workaround for ICS limitation: we are not told the wild
3733 type when starting to examine a game. But if we ask for
3734 the move list, the move list header will tell us */
3735 ics_getting_history = H_REQUESTED;
3736 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3739 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3740 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3742 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3743 /* [HGM] applied this also to an engine that is silently watching */
3744 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3745 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3746 gameInfo.variant == currentlyInitializedVariant) {
3747 takeback = forwardMostMove - moveNum;
3748 for (i = 0; i < takeback; i++) {
3749 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3750 SendToProgram("undo\n", &first);
3755 forwardMostMove = moveNum;
3756 if (!pausing || currentMove > forwardMostMove)
3757 currentMove = forwardMostMove;
3759 /* New part of history that is not contiguous with old part */
3760 if (pausing && gameMode == IcsExamining) {
3761 pauseExamInvalid = TRUE;
3762 forwardMostMove = pauseExamForwardMostMove;
3765 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3767 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3768 // [HGM] when we will receive the move list we now request, it will be
3769 // fed to the engine from the first move on. So if the engine is not
3770 // in the initial position now, bring it there.
3771 InitChessProgram(&first, 0);
3774 ics_getting_history = H_REQUESTED;
3775 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3778 forwardMostMove = backwardMostMove = currentMove = moveNum;
3781 /* Update the clocks */
3782 if (strchr(elapsed_time, '.')) {
3784 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3785 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3787 /* Time is in seconds */
3788 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3789 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3794 if (appData.zippyPlay && newGame &&
3795 gameMode != IcsObserving && gameMode != IcsIdle &&
3796 gameMode != IcsExamining)
3797 ZippyFirstBoard(moveNum, basetime, increment);
3800 /* Put the move on the move list, first converting
3801 to canonical algebraic form. */
3803 if (appData.debugMode) {
3804 if (appData.debugMode) { int f = forwardMostMove;
3805 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3806 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3808 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3809 fprintf(debugFP, "moveNum = %d\n", moveNum);
3810 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3811 setbuf(debugFP, NULL);
3813 if (moveNum <= backwardMostMove) {
3814 /* We don't know what the board looked like before
3816 strcpy(parseList[moveNum - 1], move_str);
3817 strcat(parseList[moveNum - 1], " ");
3818 strcat(parseList[moveNum - 1], elapsed_time);
3819 moveList[moveNum - 1][0] = NULLCHAR;
3820 } else if (strcmp(move_str, "none") == 0) {
3821 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3822 /* Again, we don't know what the board looked like;
3823 this is really the start of the game. */
3824 parseList[moveNum - 1][0] = NULLCHAR;
3825 moveList[moveNum - 1][0] = NULLCHAR;
3826 backwardMostMove = moveNum;
3827 startedFromSetupPosition = TRUE;
3828 fromX = fromY = toX = toY = -1;
3830 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.