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 srand(programStartTime.ms); // [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 if (appData.matchGames > 0) {
1046 appData.matchMode = TRUE;
1047 } else if (appData.matchMode) {
1048 appData.matchGames = 1;
1050 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051 appData.matchGames = appData.sameColorGames;
1052 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1057 if (appData.noChessProgram || first.protocolVersion == 1) {
1060 /* kludge: allow timeout for initial "feature" commands */
1062 DisplayMessage("", _("Starting chess program"));
1063 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1068 InitBackEnd3 P((void))
1070 GameMode initialMode;
1074 InitChessProgram(&first, startedFromSetupPosition);
1077 if (appData.icsActive) {
1079 /* [DM] Make a console window if needed [HGM] merged ifs */
1084 if (*appData.icsCommPort != NULLCHAR) {
1085 sprintf(buf, _("Could not open comm port %s"),
1086 appData.icsCommPort);
1088 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1089 appData.icsHost, appData.icsPort);
1091 DisplayFatalError(buf, err, 1);
1096 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099 } else if (appData.noChessProgram) {
1105 if (*appData.cmailGameName != NULLCHAR) {
1107 OpenLoopback(&cmailPR);
1109 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1113 DisplayMessage("", "");
1114 if (StrCaseCmp(appData.initialMode, "") == 0) {
1115 initialMode = BeginningOfGame;
1116 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117 initialMode = TwoMachinesPlay;
1118 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119 initialMode = AnalyzeFile;
1120 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121 initialMode = AnalyzeMode;
1122 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123 initialMode = MachinePlaysWhite;
1124 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125 initialMode = MachinePlaysBlack;
1126 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127 initialMode = EditGame;
1128 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129 initialMode = EditPosition;
1130 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131 initialMode = Training;
1133 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134 DisplayFatalError(buf, 0, 2);
1138 if (appData.matchMode) {
1139 /* Set up machine vs. machine match */
1140 if (appData.noChessProgram) {
1141 DisplayFatalError(_("Can't have a match with no chess programs"),
1147 if (*appData.loadGameFile != NULLCHAR) {
1148 int index = appData.loadGameIndex; // [HGM] autoinc
1149 if(index<0) lastIndex = index = 1;
1150 if (!LoadGameFromFile(appData.loadGameFile,
1152 appData.loadGameFile, FALSE)) {
1153 DisplayFatalError(_("Bad game file"), 0, 1);
1156 } else if (*appData.loadPositionFile != NULLCHAR) {
1157 int index = appData.loadPositionIndex; // [HGM] autoinc
1158 if(index<0) lastIndex = index = 1;
1159 if (!LoadPositionFromFile(appData.loadPositionFile,
1161 appData.loadPositionFile)) {
1162 DisplayFatalError(_("Bad position file"), 0, 1);
1167 } else if (*appData.cmailGameName != NULLCHAR) {
1168 /* Set up cmail mode */
1169 ReloadCmailMsgEvent(TRUE);
1171 /* Set up other modes */
1172 if (initialMode == AnalyzeFile) {
1173 if (*appData.loadGameFile == NULLCHAR) {
1174 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1178 if (*appData.loadGameFile != NULLCHAR) {
1179 (void) LoadGameFromFile(appData.loadGameFile,
1180 appData.loadGameIndex,
1181 appData.loadGameFile, TRUE);
1182 } else if (*appData.loadPositionFile != NULLCHAR) {
1183 (void) LoadPositionFromFile(appData.loadPositionFile,
1184 appData.loadPositionIndex,
1185 appData.loadPositionFile);
1186 /* [HGM] try to make self-starting even after FEN load */
1187 /* to allow automatic setup of fairy variants with wtm */
1188 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189 gameMode = BeginningOfGame;
1190 setboardSpoiledMachineBlack = 1;
1192 /* [HGM] loadPos: make that every new game uses the setup */
1193 /* from file as long as we do not switch variant */
1194 if(!blackPlaysFirst) { int i;
1195 startedFromPositionFile = TRUE;
1196 CopyBoard(filePosition, boards[0]);
1197 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1200 if (initialMode == AnalyzeMode) {
1201 if (appData.noChessProgram) {
1202 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1205 if (appData.icsActive) {
1206 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1210 } else if (initialMode == AnalyzeFile) {
1211 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212 ShowThinkingEvent();
1214 AnalysisPeriodicEvent(1);
1215 } else if (initialMode == MachinePlaysWhite) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1221 if (appData.icsActive) {
1222 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1226 MachineWhiteEvent();
1227 } else if (initialMode == MachinePlaysBlack) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1238 MachineBlackEvent();
1239 } else if (initialMode == TwoMachinesPlay) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1251 } else if (initialMode == EditGame) {
1253 } else if (initialMode == EditPosition) {
1254 EditPositionEvent();
1255 } else if (initialMode == Training) {
1256 if (*appData.loadGameFile == NULLCHAR) {
1257 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1266 * Establish will establish a contact to a remote host.port.
1267 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268 * used to talk to the host.
1269 * Returns 0 if okay, error code if not.
1276 if (*appData.icsCommPort != NULLCHAR) {
1277 /* Talk to the host through a serial comm port */
1278 return OpenCommPort(appData.icsCommPort, &icsPR);
1280 } else if (*appData.gateway != NULLCHAR) {
1281 if (*appData.remoteShell == NULLCHAR) {
1282 /* Use the rcmd protocol to run telnet program on a gateway host */
1283 snprintf(buf, sizeof(buf), "%s %s %s",
1284 appData.telnetProgram, appData.icsHost, appData.icsPort);
1285 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1288 /* Use the rsh program to run telnet program on a gateway host */
1289 if (*appData.remoteUser == NULLCHAR) {
1290 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291 appData.gateway, appData.telnetProgram,
1292 appData.icsHost, appData.icsPort);
1294 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295 appData.remoteShell, appData.gateway,
1296 appData.remoteUser, appData.telnetProgram,
1297 appData.icsHost, appData.icsPort);
1299 return StartChildProcess(buf, "", &icsPR);
1302 } else if (appData.useTelnet) {
1303 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1306 /* TCP socket interface differs somewhat between
1307 Unix and NT; handle details in the front end.
1309 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1314 show_bytes(fp, buf, count)
1320 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321 fprintf(fp, "\\%03o", *buf & 0xff);
1330 /* Returns an errno value */
1332 OutputMaybeTelnet(pr, message, count, outError)
1338 char buf[8192], *p, *q, *buflim;
1339 int left, newcount, outcount;
1341 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342 *appData.gateway != NULLCHAR) {
1343 if (appData.debugMode) {
1344 fprintf(debugFP, ">ICS: ");
1345 show_bytes(debugFP, message, count);
1346 fprintf(debugFP, "\n");
1348 return OutputToProcess(pr, message, count, outError);
1351 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, buf, newcount);
1361 fprintf(debugFP, "\n");
1363 outcount = OutputToProcess(pr, buf, newcount, outError);
1364 if (outcount < newcount) return -1; /* to be sure */
1371 } else if (((unsigned char) *p) == TN_IAC) {
1372 *q++ = (char) TN_IAC;
1379 if (appData.debugMode) {
1380 fprintf(debugFP, ">ICS: ");
1381 show_bytes(debugFP, buf, newcount);
1382 fprintf(debugFP, "\n");
1384 outcount = OutputToProcess(pr, buf, newcount, outError);
1385 if (outcount < newcount) return -1; /* to be sure */
1390 read_from_player(isr, closure, message, count, error)
1397 int outError, outCount;
1398 static int gotEof = 0;
1400 /* Pass data read from player on to ICS */
1403 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404 if (outCount < count) {
1405 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407 } else if (count < 0) {
1408 RemoveInputSource(isr);
1409 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410 } else if (gotEof++ > 0) {
1411 RemoveInputSource(isr);
1412 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1418 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419 SendToICS("date\n");
1420 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1426 char buffer[MSG_SIZ];
1429 va_start(args, format);
1430 vsnprintf(buffer, sizeof(buffer), format, args);
1431 buffer[sizeof(buffer)-1] = '\0';
1440 int count, outCount, outError;
1442 if (icsPR == NULL) return;
1445 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1446 if (outCount < count) {
1447 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1451 /* This is used for sending logon scripts to the ICS. Sending
1452 without a delay causes problems when using timestamp on ICC
1453 (at least on my machine). */
1455 SendToICSDelayed(s,msdelay)
1459 int count, outCount, outError;
1461 if (icsPR == NULL) return;
1464 if (appData.debugMode) {
1465 fprintf(debugFP, ">ICS: ");
1466 show_bytes(debugFP, s, count);
1467 fprintf(debugFP, "\n");
1469 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471 if (outCount < count) {
1472 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1477 /* Remove all highlighting escape sequences in s
1478 Also deletes any suffix starting with '('
1481 StripHighlightAndTitle(s)
1484 static char retbuf[MSG_SIZ];
1487 while (*s != NULLCHAR) {
1488 while (*s == '\033') {
1489 while (*s != NULLCHAR && !isalpha(*s)) s++;
1490 if (*s != NULLCHAR) s++;
1492 while (*s != NULLCHAR && *s != '\033') {
1493 if (*s == '(' || *s == '[') {
1504 /* Remove all highlighting escape sequences in s */
1509 static char retbuf[MSG_SIZ];
1512 while (*s != NULLCHAR) {
1513 while (*s == '\033') {
1514 while (*s != NULLCHAR && !isalpha(*s)) s++;
1515 if (*s != NULLCHAR) s++;
1517 while (*s != NULLCHAR && *s != '\033') {
1525 char *variantNames[] = VARIANT_NAMES;
1530 return variantNames[v];
1534 /* Identify a variant from the strings the chess servers use or the
1535 PGN Variant tag names we use. */
1542 VariantClass v = VariantNormal;
1543 int i, found = FALSE;
1548 /* [HGM] skip over optional board-size prefixes */
1549 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1550 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1551 while( *e++ != '_');
1554 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1558 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1559 if (StrCaseStr(e, variantNames[i])) {
1560 v = (VariantClass) i;
1567 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1568 || StrCaseStr(e, "wild/fr")
1569 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1570 v = VariantFischeRandom;
1571 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1572 (i = 1, p = StrCaseStr(e, "w"))) {
1574 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1581 case 0: /* FICS only, actually */
1583 /* Castling legal even if K starts on d-file */
1584 v = VariantWildCastle;
1589 /* Castling illegal even if K & R happen to start in
1590 normal positions. */
1591 v = VariantNoCastle;
1604 /* Castling legal iff K & R start in normal positions */
1610 /* Special wilds for position setup; unclear what to do here */
1611 v = VariantLoadable;
1614 /* Bizarre ICC game */
1615 v = VariantTwoKings;
1618 v = VariantKriegspiel;
1624 v = VariantFischeRandom;
1627 v = VariantCrazyhouse;
1630 v = VariantBughouse;
1636 /* Not quite the same as FICS suicide! */
1637 v = VariantGiveaway;
1643 v = VariantShatranj;
1646 /* Temporary names for future ICC types. The name *will* change in
1647 the next xboard/WinBoard release after ICC defines it. */
1685 v = VariantCapablanca;
1688 v = VariantKnightmate;
1694 v = VariantCylinder;
1700 v = VariantCapaRandom;
1703 v = VariantBerolina;
1715 /* Found "wild" or "w" in the string but no number;
1716 must assume it's normal chess. */
1720 sprintf(buf, _("Unknown wild type %d"), wnum);
1721 DisplayError(buf, 0);
1727 if (appData.debugMode) {
1728 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1729 e, wnum, VariantName(v));
1734 static int leftover_start = 0, leftover_len = 0;
1735 char star_match[STAR_MATCH_N][MSG_SIZ];
1737 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1738 advance *index beyond it, and set leftover_start to the new value of
1739 *index; else return FALSE. If pattern contains the character '*', it
1740 matches any sequence of characters not containing '\r', '\n', or the
1741 character following the '*' (if any), and the matched sequence(s) are
1742 copied into star_match.
1745 looking_at(buf, index, pattern)
1750 char *bufp = &buf[*index], *patternp = pattern;
1752 char *matchp = star_match[0];
1755 if (*patternp == NULLCHAR) {
1756 *index = leftover_start = bufp - buf;
1760 if (*bufp == NULLCHAR) return FALSE;
1761 if (*patternp == '*') {
1762 if (*bufp == *(patternp + 1)) {
1764 matchp = star_match[++star_count];
1768 } else if (*bufp == '\n' || *bufp == '\r') {
1770 if (*patternp == NULLCHAR)
1775 *matchp++ = *bufp++;
1779 if (*patternp != *bufp) return FALSE;
1786 SendToPlayer(data, length)
1790 int error, outCount;
1791 outCount = OutputToProcess(NoProc, data, length, &error);
1792 if (outCount < length) {
1793 DisplayFatalError(_("Error writing to display"), error, 1);
1798 PackHolding(packed, holding)
1810 switch (runlength) {
1821 sprintf(q, "%d", runlength);
1833 /* Telnet protocol requests from the front end */
1835 TelnetRequest(ddww, option)
1836 unsigned char ddww, option;
1838 unsigned char msg[3];
1839 int outCount, outError;
1841 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843 if (appData.debugMode) {
1844 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860 sprintf(buf1, "%d", ddww);
1869 sprintf(buf2, "%d", option);
1872 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1877 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1886 if (!appData.icsActive) return;
1887 TelnetRequest(TN_DO, TN_ECHO);
1893 if (!appData.icsActive) return;
1894 TelnetRequest(TN_DONT, TN_ECHO);
1898 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 /* put the holdings sent to us by the server on the board holdings area */
1901 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1905 if(gameInfo.holdingsWidth < 2) return;
1907 if( (int)lowestPiece >= BlackPawn ) {
1910 holdingsStartRow = BOARD_HEIGHT-1;
1913 holdingsColumn = BOARD_WIDTH-1;
1914 countsColumn = BOARD_WIDTH-2;
1915 holdingsStartRow = 0;
1919 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920 board[i][holdingsColumn] = EmptySquare;
1921 board[i][countsColumn] = (ChessSquare) 0;
1923 while( (p=*holdings++) != NULLCHAR ) {
1924 piece = CharToPiece( ToUpper(p) );
1925 if(piece == EmptySquare) continue;
1926 /*j = (int) piece - (int) WhitePawn;*/
1927 j = PieceToNumber(piece);
1928 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929 if(j < 0) continue; /* should not happen */
1930 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932 board[holdingsStartRow+j*direction][countsColumn]++;
1939 VariantSwitch(Board board, VariantClass newVariant)
1941 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943 startedFromPositionFile = FALSE;
1944 if(gameInfo.variant == newVariant) return;
1946 /* [HGM] This routine is called each time an assignment is made to
1947 * gameInfo.variant during a game, to make sure the board sizes
1948 * are set to match the new variant. If that means adding or deleting
1949 * holdings, we shift the playing board accordingly
1950 * This kludge is needed because in ICS observe mode, we get boards
1951 * of an ongoing game without knowing the variant, and learn about the
1952 * latter only later. This can be because of the move list we requested,
1953 * in which case the game history is refilled from the beginning anyway,
1954 * but also when receiving holdings of a crazyhouse game. In the latter
1955 * case we want to add those holdings to the already received position.
1959 if (appData.debugMode) {
1960 fprintf(debugFP, "Switch board from %s to %s\n",
1961 VariantName(gameInfo.variant), VariantName(newVariant));
1962 setbuf(debugFP, NULL);
1964 shuffleOpenings = 0; /* [HGM] shuffle */
1965 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969 newWidth = 9; newHeight = 9;
1970 gameInfo.holdingsSize = 7;
1971 case VariantBughouse:
1972 case VariantCrazyhouse:
1973 newHoldingsWidth = 2; break;
1977 newHoldingsWidth = 2;
1978 gameInfo.holdingsSize = 8;
1981 case VariantCapablanca:
1982 case VariantCapaRandom:
1985 newHoldingsWidth = gameInfo.holdingsSize = 0;
1988 if(newWidth != gameInfo.boardWidth ||
1989 newHeight != gameInfo.boardHeight ||
1990 newHoldingsWidth != gameInfo.holdingsWidth ) {
1992 /* shift position to new playing area, if needed */
1993 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994 for(i=0; i<BOARD_HEIGHT; i++)
1995 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998 for(i=0; i<newHeight; i++) {
1999 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003 for(i=0; i<BOARD_HEIGHT; i++)
2004 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2008 gameInfo.boardWidth = newWidth;
2009 gameInfo.boardHeight = newHeight;
2010 gameInfo.holdingsWidth = newHoldingsWidth;
2011 gameInfo.variant = newVariant;
2012 InitDrawingSizes(-2, 0);
2013 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2014 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2016 DrawPosition(TRUE, boards[currentMove]);
2019 static int loggedOn = FALSE;
2021 /*-- Game start info cache: --*/
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static char cont_seq[] = "\n\\ ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2035 read_from_ics(isr, closure, data, count, error)
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2052 static int started = STARTED_NONE;
2053 static char parse[20000];
2054 static int parse_pos = 0;
2055 static char buf[BUF_SIZE + 1];
2056 static int firstTime = TRUE, intfSet = FALSE;
2057 static ColorClass prevColor = ColorNormal;
2058 static int savingComment = FALSE;
2059 static int cmatch = 0; // continuation sequence match
2066 int backup; /* [DM] For zippy color lines */
2068 char talker[MSG_SIZ]; // [HGM] chat
2071 if (appData.debugMode) {
2073 fprintf(debugFP, "<ICS: ");
2074 show_bytes(debugFP, data, count);
2075 fprintf(debugFP, "\n");
2079 if (appData.debugMode) { int f = forwardMostMove;
2080 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2084 /* If last read ended with a partial line that we couldn't parse,
2085 prepend it to the new read and try again. */
2086 if (leftover_len > 0) {
2087 for (i=0; i<leftover_len; i++)
2088 buf[i] = buf[leftover_start + i];
2091 /* copy new characters into the buffer */
2092 bp = buf + leftover_len;
2093 buf_len=leftover_len;
2094 for (i=0; i<count; i++)
2097 if (data[i] == '\r')
2100 // join lines split by ICS?
2101 if (!appData.noJoin)
2104 Joining just consists of finding matches against the
2105 continuation sequence, and discarding that sequence
2106 if found instead of copying it. So, until a match
2107 fails, there's nothing to do since it might be the
2108 complete sequence, and thus, something we don't want
2111 if (data[i] == cont_seq[cmatch])
2114 if (cmatch == strlen(cont_seq))
2116 cmatch = 0; // complete match. just reset the counter
2119 it's possible for the ICS to not include the space
2120 at the end of the last word, making our [correct]
2121 join operation fuse two separate words. the server
2122 does this when the space occurs at the width setting.
2124 if (!buf_len || buf[buf_len-1] != ' ')
2135 match failed, so we have to copy what matched before
2136 falling through and copying this character. In reality,
2137 this will only ever be just the newline character, but
2138 it doesn't hurt to be precise.
2140 strncpy(bp, cont_seq, cmatch);
2152 buf[buf_len] = NULLCHAR;
2153 next_out = leftover_len;
2157 while (i < buf_len) {
2158 /* Deal with part of the TELNET option negotiation
2159 protocol. We refuse to do anything beyond the
2160 defaults, except that we allow the WILL ECHO option,
2161 which ICS uses to turn off password echoing when we are
2162 directly connected to it. We reject this option
2163 if localLineEditing mode is on (always on in xboard)
2164 and we are talking to port 23, which might be a real
2165 telnet server that will try to keep WILL ECHO on permanently.
2167 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2168 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2169 unsigned char option;
2171 switch ((unsigned char) buf[++i]) {
2173 if (appData.debugMode)
2174 fprintf(debugFP, "\n<WILL ");
2175 switch (option = (unsigned char) buf[++i]) {
2177 if (appData.debugMode)
2178 fprintf(debugFP, "ECHO ");
2179 /* Reply only if this is a change, according
2180 to the protocol rules. */
2181 if (remoteEchoOption) break;
2182 if (appData.localLineEditing &&
2183 atoi(appData.icsPort) == TN_PORT) {
2184 TelnetRequest(TN_DONT, TN_ECHO);
2187 TelnetRequest(TN_DO, TN_ECHO);
2188 remoteEchoOption = TRUE;
2192 if (appData.debugMode)
2193 fprintf(debugFP, "%d ", option);
2194 /* Whatever this is, we don't want it. */
2195 TelnetRequest(TN_DONT, option);
2200 if (appData.debugMode)
2201 fprintf(debugFP, "\n<WONT ");
2202 switch (option = (unsigned char) buf[++i]) {
2204 if (appData.debugMode)
2205 fprintf(debugFP, "ECHO ");
2206 /* Reply only if this is a change, according
2207 to the protocol rules. */
2208 if (!remoteEchoOption) break;
2210 TelnetRequest(TN_DONT, TN_ECHO);
2211 remoteEchoOption = FALSE;
2214 if (appData.debugMode)
2215 fprintf(debugFP, "%d ", (unsigned char) option);
2216 /* Whatever this is, it must already be turned
2217 off, because we never agree to turn on
2218 anything non-default, so according to the
2219 protocol rules, we don't reply. */
2224 if (appData.debugMode)
2225 fprintf(debugFP, "\n<DO ");
2226 switch (option = (unsigned char) buf[++i]) {
2228 /* Whatever this is, we refuse to do it. */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "%d ", option);
2231 TelnetRequest(TN_WONT, option);
2236 if (appData.debugMode)
2237 fprintf(debugFP, "\n<DONT ");
2238 switch (option = (unsigned char) buf[++i]) {
2240 if (appData.debugMode)
2241 fprintf(debugFP, "%d ", option);
2242 /* Whatever this is, we are already not doing
2243 it, because we never agree to do anything
2244 non-default, so according to the protocol
2245 rules, we don't reply. */
2250 if (appData.debugMode)
2251 fprintf(debugFP, "\n<IAC ");
2252 /* Doubled IAC; pass it through */
2256 if (appData.debugMode)
2257 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2258 /* Drop all other telnet commands on the floor */
2261 if (oldi > next_out)
2262 SendToPlayer(&buf[next_out], oldi - next_out);
2268 /* OK, this at least will *usually* work */
2269 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2273 if (loggedOn && !intfSet) {
2274 if (ics_type == ICS_ICC) {
2276 "/set-quietly interface %s\n/set-quietly style 12\n",
2278 } else if (ics_type == ICS_CHESSNET) {
2279 sprintf(str, "/style 12\n");
2281 strcpy(str, "alias $ @\n$set interface ");
2282 strcat(str, programVersion);
2283 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 strcat(str, "$iset nohighlight 1\n");
2287 strcat(str, "$iset lock 1\n$style 12\n");
2290 NotifyFrontendLogin();
2294 if (started == STARTED_COMMENT) {
2295 /* Accumulate characters in comment */
2296 parse[parse_pos++] = buf[i];
2297 if (buf[i] == '\n') {
2298 parse[parse_pos] = NULLCHAR;
2299 if(chattingPartner>=0) {
2301 sprintf(mess, "%s%s", talker, parse);
2302 OutputChatMessage(chattingPartner, mess);
2303 chattingPartner = -1;
2305 if(!suppressKibitz) // [HGM] kibitz
2306 AppendComment(forwardMostMove, StripHighlight(parse));
2307 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2308 int nrDigit = 0, nrAlph = 0, i;
2309 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2310 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2311 parse[parse_pos] = NULLCHAR;
2312 // try to be smart: if it does not look like search info, it should go to
2313 // ICS interaction window after all, not to engine-output window.
2314 for(i=0; i<parse_pos; i++) { // count letters and digits
2315 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2316 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2317 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2319 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2320 int depth=0; float score;
2321 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2322 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2323 pvInfoList[forwardMostMove-1].depth = depth;
2324 pvInfoList[forwardMostMove-1].score = 100*score;
2326 OutputKibitz(suppressKibitz, parse);
2329 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2330 SendToPlayer(tmp, strlen(tmp));
2333 started = STARTED_NONE;
2335 /* Don't match patterns against characters in chatter */
2340 if (started == STARTED_CHATTER) {
2341 if (buf[i] != '\n') {
2342 /* Don't match patterns against characters in chatter */
2346 started = STARTED_NONE;
2349 /* Kludge to deal with rcmd protocol */
2350 if (firstTime && looking_at(buf, &i, "\001*")) {
2351 DisplayFatalError(&buf[1], 0, 1);
2357 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2360 if (appData.debugMode)
2361 fprintf(debugFP, "ics_type %d\n", ics_type);
2364 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2365 ics_type = ICS_FICS;
2367 if (appData.debugMode)
2368 fprintf(debugFP, "ics_type %d\n", ics_type);
2371 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2372 ics_type = ICS_CHESSNET;
2374 if (appData.debugMode)
2375 fprintf(debugFP, "ics_type %d\n", ics_type);
2380 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2381 looking_at(buf, &i, "Logging you in as \"*\"") ||
2382 looking_at(buf, &i, "will be \"*\""))) {
2383 strcpy(ics_handle, star_match[0]);
2387 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2390 DisplayIcsInteractionTitle(buf);
2391 have_set_title = TRUE;
2394 /* skip finger notes */
2395 if (started == STARTED_NONE &&
2396 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2397 (buf[i] == '1' && buf[i+1] == '0')) &&
2398 buf[i+2] == ':' && buf[i+3] == ' ') {
2399 started = STARTED_CHATTER;
2404 /* skip formula vars */
2405 if (started == STARTED_NONE &&
2406 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2407 started = STARTED_CHATTER;
2413 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2414 if (appData.autoKibitz && started == STARTED_NONE &&
2415 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2416 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2417 if(looking_at(buf, &i, "* kibitzes: ") &&
2418 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2419 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2420 suppressKibitz = TRUE;
2421 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2422 && (gameMode == IcsPlayingWhite)) ||
2423 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2424 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2425 started = STARTED_CHATTER; // own kibitz we simply discard
2427 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2428 parse_pos = 0; parse[0] = NULLCHAR;
2429 savingComment = TRUE;
2430 suppressKibitz = gameMode != IcsObserving ? 2 :
2431 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2435 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2436 started = STARTED_CHATTER;
2437 suppressKibitz = TRUE;
2439 } // [HGM] kibitz: end of patch
2441 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443 // [HGM] chat: intercept tells by users for which we have an open chat window
2445 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2446 looking_at(buf, &i, "* whispers:") ||
2447 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2448 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2451 chattingPartner = -1;
2453 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2454 for(p=0; p<MAX_CHAT; p++) {
2455 if(channel == atoi(chatPartner[p])) {
2456 talker[0] = '['; strcat(talker, "]");
2457 chattingPartner = p; break;
2460 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2461 for(p=0; p<MAX_CHAT; p++) {
2462 if(!strcmp("WHISPER", chatPartner[p])) {
2463 talker[0] = '['; strcat(talker, "]");
2464 chattingPartner = p; break;
2467 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2468 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470 chattingPartner = p; break;
2472 if(chattingPartner<0) i = oldi; else {
2473 started = STARTED_COMMENT;
2474 parse_pos = 0; parse[0] = NULLCHAR;
2475 savingComment = TRUE;
2476 suppressKibitz = TRUE;
2478 } // [HGM] chat: end of patch
2480 if (appData.zippyTalk || appData.zippyPlay) {
2481 /* [DM] Backup address for color zippy lines */
2485 if (loggedOn == TRUE)
2486 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2487 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489 if (ZippyControl(buf, &i) ||
2490 ZippyConverse(buf, &i) ||
2491 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493 if (!appData.colorize) continue;
2497 } // [DM] 'else { ' deleted
2499 /* Regular tells and says */
2500 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2501 looking_at(buf, &i, "* (your partner) tells you: ") ||
2502 looking_at(buf, &i, "* says: ") ||
2503 /* Don't color "message" or "messages" output */
2504 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2505 looking_at(buf, &i, "*. * at *:*: ") ||
2506 looking_at(buf, &i, "--* (*:*): ") ||
2507 /* Message notifications (same color as tells) */
2508 looking_at(buf, &i, "* has left a message ") ||
2509 looking_at(buf, &i, "* just sent you a message:\n") ||
2510 /* Whispers and kibitzes */
2511 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2512 looking_at(buf, &i, "* kibitzes: ") ||
2514 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516 if (tkind == 1 && strchr(star_match[0], ':')) {
2517 /* Avoid "tells you:" spoofs in channels */
2520 if (star_match[0][0] == NULLCHAR ||
2521 strchr(star_match[0], ' ') ||
2522 (tkind == 3 && strchr(star_match[1], ' '))) {
2523 /* Reject bogus matches */
2526 if (appData.colorize) {
2527 if (oldi > next_out) {
2528 SendToPlayer(&buf[next_out], oldi - next_out);
2533 Colorize(ColorTell, FALSE);
2534 curColor = ColorTell;
2537 Colorize(ColorKibitz, FALSE);
2538 curColor = ColorKibitz;
2541 p = strrchr(star_match[1], '(');
2548 Colorize(ColorChannel1, FALSE);
2549 curColor = ColorChannel1;
2551 Colorize(ColorChannel, FALSE);
2552 curColor = ColorChannel;
2556 curColor = ColorNormal;
2560 if (started == STARTED_NONE && appData.autoComment &&
2561 (gameMode == IcsObserving ||
2562 gameMode == IcsPlayingWhite ||
2563 gameMode == IcsPlayingBlack)) {
2564 parse_pos = i - oldi;
2565 memcpy(parse, &buf[oldi], parse_pos);
2566 parse[parse_pos] = NULLCHAR;
2567 started = STARTED_COMMENT;
2568 savingComment = TRUE;
2570 started = STARTED_CHATTER;
2571 savingComment = FALSE;
2578 if (looking_at(buf, &i, "* s-shouts: ") ||
2579 looking_at(buf, &i, "* c-shouts: ")) {
2580 if (appData.colorize) {
2581 if (oldi > next_out) {
2582 SendToPlayer(&buf[next_out], oldi - next_out);
2585 Colorize(ColorSShout, FALSE);
2586 curColor = ColorSShout;
2589 started = STARTED_CHATTER;
2593 if (looking_at(buf, &i, "--->")) {
2598 if (looking_at(buf, &i, "* shouts: ") ||
2599 looking_at(buf, &i, "--> ")) {
2600 if (appData.colorize) {
2601 if (oldi > next_out) {
2602 SendToPlayer(&buf[next_out], oldi - next_out);
2605 Colorize(ColorShout, FALSE);
2606 curColor = ColorShout;
2609 started = STARTED_CHATTER;
2613 if (looking_at( buf, &i, "Challenge:")) {
2614 if (appData.colorize) {
2615 if (oldi > next_out) {
2616 SendToPlayer(&buf[next_out], oldi - next_out);
2619 Colorize(ColorChallenge, FALSE);
2620 curColor = ColorChallenge;
2626 if (looking_at(buf, &i, "* offers you") ||
2627 looking_at(buf, &i, "* offers to be") ||
2628 looking_at(buf, &i, "* would like to") ||
2629 looking_at(buf, &i, "* requests to") ||
2630 looking_at(buf, &i, "Your opponent offers") ||
2631 looking_at(buf, &i, "Your opponent requests")) {
2633 if (appData.colorize) {
2634 if (oldi > next_out) {
2635 SendToPlayer(&buf[next_out], oldi - next_out);
2638 Colorize(ColorRequest, FALSE);
2639 curColor = ColorRequest;
2644 if (looking_at(buf, &i, "* (*) seeking")) {
2645 if (appData.colorize) {
2646 if (oldi > next_out) {
2647 SendToPlayer(&buf[next_out], oldi - next_out);
2650 Colorize(ColorSeek, FALSE);
2651 curColor = ColorSeek;
2656 if (looking_at(buf, &i, "\\ ")) {
2657 if (prevColor != ColorNormal) {
2658 if (oldi > next_out) {
2659 SendToPlayer(&buf[next_out], oldi - next_out);
2662 Colorize(prevColor, TRUE);
2663 curColor = prevColor;
2665 if (savingComment) {
2666 parse_pos = i - oldi;
2667 memcpy(parse, &buf[oldi], parse_pos);
2668 parse[parse_pos] = NULLCHAR;
2669 started = STARTED_COMMENT;
2671 started = STARTED_CHATTER;
2676 if (looking_at(buf, &i, "Black Strength :") ||
2677 looking_at(buf, &i, "<<< style 10 board >>>") ||
2678 looking_at(buf, &i, "<10>") ||
2679 looking_at(buf, &i, "#@#")) {
2680 /* Wrong board style */
2682 SendToICS(ics_prefix);
2683 SendToICS("set style 12\n");
2684 SendToICS(ics_prefix);
2685 SendToICS("refresh\n");
2689 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691 have_sent_ICS_logon = 1;
2695 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2696 (looking_at(buf, &i, "\n<12> ") ||
2697 looking_at(buf, &i, "<12> "))) {
2699 if (oldi > next_out) {
2700 SendToPlayer(&buf[next_out], oldi - next_out);
2703 started = STARTED_BOARD;
2708 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2709 looking_at(buf, &i, "<b1> ")) {
2710 if (oldi > next_out) {
2711 SendToPlayer(&buf[next_out], oldi - next_out);
2714 started = STARTED_HOLDINGS;
2719 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721 /* Header for a move list -- first line */
2723 switch (ics_getting_history) {
2727 case BeginningOfGame:
2728 /* User typed "moves" or "oldmoves" while we
2729 were idle. Pretend we asked for these
2730 moves and soak them up so user can step
2731 through them and/or save them.
2734 gameMode = IcsObserving;
2737 ics_getting_history = H_GOT_UNREQ_HEADER;
2739 case EditGame: /*?*/
2740 case EditPosition: /*?*/
2741 /* Should above feature work in these modes too? */
2742 /* For now it doesn't */
2743 ics_getting_history = H_GOT_UNWANTED_HEADER;
2746 ics_getting_history = H_GOT_UNWANTED_HEADER;
2751 /* Is this the right one? */
2752 if (gameInfo.white && gameInfo.black &&
2753 strcmp(gameInfo.white, star_match[0]) == 0 &&
2754 strcmp(gameInfo.black, star_match[2]) == 0) {
2756 ics_getting_history = H_GOT_REQ_HEADER;
2759 case H_GOT_REQ_HEADER:
2760 case H_GOT_UNREQ_HEADER:
2761 case H_GOT_UNWANTED_HEADER:
2762 case H_GETTING_MOVES:
2763 /* Should not happen */
2764 DisplayError(_("Error gathering move list: two headers"), 0);
2765 ics_getting_history = H_FALSE;
2769 /* Save player ratings into gameInfo if needed */
2770 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2771 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2772 (gameInfo.whiteRating == -1 ||
2773 gameInfo.blackRating == -1)) {
2775 gameInfo.whiteRating = string_to_rating(star_match[1]);
2776 gameInfo.blackRating = string_to_rating(star_match[3]);
2777 if (appData.debugMode)
2778 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2779 gameInfo.whiteRating, gameInfo.blackRating);
2784 if (looking_at(buf, &i,
2785 "* * match, initial time: * minute*, increment: * second")) {
2786 /* Header for a move list -- second line */
2787 /* Initial board will follow if this is a wild game */
2788 if (gameInfo.event != NULL) free(gameInfo.event);
2789 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2790 gameInfo.event = StrSave(str);
2791 /* [HGM] we switched variant. Translate boards if needed. */
2792 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2796 if (looking_at(buf, &i, "Move ")) {
2797 /* Beginning of a move list */
2798 switch (ics_getting_history) {
2800 /* Normally should not happen */
2801 /* Maybe user hit reset while we were parsing */
2804 /* Happens if we are ignoring a move list that is not
2805 * the one we just requested. Common if the user
2806 * tries to observe two games without turning off
2809 case H_GETTING_MOVES:
2810 /* Should not happen */
2811 DisplayError(_("Error gathering move list: nested"), 0);
2812 ics_getting_history = H_FALSE;
2814 case H_GOT_REQ_HEADER:
2815 ics_getting_history = H_GETTING_MOVES;
2816 started = STARTED_MOVES;
2818 if (oldi > next_out) {
2819 SendToPlayer(&buf[next_out], oldi - next_out);
2822 case H_GOT_UNREQ_HEADER:
2823 ics_getting_history = H_GETTING_MOVES;
2824 started = STARTED_MOVES_NOHIDE;
2827 case H_GOT_UNWANTED_HEADER:
2828 ics_getting_history = H_FALSE;
2834 if (looking_at(buf, &i, "% ") ||
2835 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2836 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2837 savingComment = FALSE;
2840 case STARTED_MOVES_NOHIDE:
2841 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2842 parse[parse_pos + i - oldi] = NULLCHAR;
2843 ParseGameHistory(parse);
2845 if (appData.zippyPlay && first.initDone) {
2846 FeedMovesToProgram(&first, forwardMostMove);
2847 if (gameMode == IcsPlayingWhite) {
2848 if (WhiteOnMove(forwardMostMove)) {
2849 if (first.sendTime) {
2850 if (first.useColors) {
2851 SendToProgram("black\n", &first);
2853 SendTimeRemaining(&first, TRUE);
2855 if (first.useColors) {
2856 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2859 first.maybeThinking = TRUE;
2861 if (first.usePlayother) {
2862 if (first.sendTime) {
2863 SendTimeRemaining(&first, TRUE);
2865 SendToProgram("playother\n", &first);
2871 } else if (gameMode == IcsPlayingBlack) {
2872 if (!WhiteOnMove(forwardMostMove)) {
2873 if (first.sendTime) {
2874 if (first.useColors) {
2875 SendToProgram("white\n", &first);
2877 SendTimeRemaining(&first, FALSE);
2879 if (first.useColors) {
2880 SendToProgram("black\n", &first);
2882 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2883 first.maybeThinking = TRUE;
2885 if (first.usePlayother) {
2886 if (first.sendTime) {
2887 SendTimeRemaining(&first, FALSE);
2889 SendToProgram("playother\n", &first);
2898 if (gameMode == IcsObserving && ics_gamenum == -1) {
2899 /* Moves came from oldmoves or moves command
2900 while we weren't doing anything else.
2902 currentMove = forwardMostMove;
2903 ClearHighlights();/*!!could figure this out*/
2904 flipView = appData.flipView;
2905 DrawPosition(FALSE, boards[currentMove]);
2906 DisplayBothClocks();
2907 sprintf(str, "%s vs. %s",
2908 gameInfo.white, gameInfo.black);
2912 /* Moves were history of an active game */
2913 if (gameInfo.resultDetails != NULL) {
2914 free(gameInfo.resultDetails);
2915 gameInfo.resultDetails = NULL;
2918 HistorySet(parseList, backwardMostMove,
2919 forwardMostMove, currentMove-1);
2920 DisplayMove(currentMove - 1);
2921 if (started == STARTED_MOVES) next_out = i;
2922 started = STARTED_NONE;
2923 ics_getting_history = H_FALSE;
2926 case STARTED_OBSERVE:
2927 started = STARTED_NONE;
2928 SendToICS(ics_prefix);
2929 SendToICS("refresh\n");
2935 if(bookHit) { // [HGM] book: simulate book reply
2936 static char bookMove[MSG_SIZ]; // a bit generous?
2938 programStats.nodes = programStats.depth = programStats.time =
2939 programStats.score = programStats.got_only_move = 0;
2940 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942 strcpy(bookMove, "move ");
2943 strcat(bookMove, bookHit);
2944 HandleMachineMove(bookMove, &first);
2949 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2950 started == STARTED_HOLDINGS ||
2951 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2952 /* Accumulate characters in move list or board */
2953 parse[parse_pos++] = buf[i];
2956 /* Start of game messages. Mostly we detect start of game
2957 when the first board image arrives. On some versions
2958 of the ICS, though, we need to do a "refresh" after starting
2959 to observe in order to get the current board right away. */
2960 if (looking_at(buf, &i, "Adding game * to observation list")) {
2961 started = STARTED_OBSERVE;
2965 /* Handle auto-observe */
2966 if (appData.autoObserve &&
2967 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2968 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970 /* Choose the player that was highlighted, if any. */
2971 if (star_match[0][0] == '\033' ||
2972 star_match[1][0] != '\033') {
2973 player = star_match[0];
2975 player = star_match[2];
2977 sprintf(str, "%sobserve %s\n",
2978 ics_prefix, StripHighlightAndTitle(player));
2981 /* Save ratings from notify string */
2982 strcpy(player1Name, star_match[0]);
2983 player1Rating = string_to_rating(star_match[1]);
2984 strcpy(player2Name, star_match[2]);
2985 player2Rating = string_to_rating(star_match[3]);
2987 if (appData.debugMode)
2989 "Ratings from 'Game notification:' %s %d, %s %d\n",
2990 player1Name, player1Rating,
2991 player2Name, player2Rating);
2996 /* Deal with automatic examine mode after a game,
2997 and with IcsObserving -> IcsExamining transition */
2998 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2999 looking_at(buf, &i, "has made you an examiner of game *")) {
3001 int gamenum = atoi(star_match[0]);
3002 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3003 gamenum == ics_gamenum) {
3004 /* We were already playing or observing this game;
3005 no need to refetch history */
3006 gameMode = IcsExamining;
3008 pauseExamForwardMostMove = forwardMostMove;
3009 } else if (currentMove < forwardMostMove) {
3010 ForwardInner(forwardMostMove);
3013 /* I don't think this case really can happen */
3014 SendToICS(ics_prefix);
3015 SendToICS("refresh\n");
3020 /* Error messages */
3021 // if (ics_user_moved) {
3022 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3023 if (looking_at(buf, &i, "Illegal move") ||
3024 looking_at(buf, &i, "Not a legal move") ||
3025 looking_at(buf, &i, "Your king is in check") ||
3026 looking_at(buf, &i, "It isn't your turn") ||
3027 looking_at(buf, &i, "It is not your move")) {
3029 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3030 currentMove = --forwardMostMove;
3031 DisplayMove(currentMove - 1); /* before DMError */
3032 DrawPosition(FALSE, boards[currentMove]);
3034 DisplayBothClocks();
3036 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3042 if (looking_at(buf, &i, "still have time") ||
3043 looking_at(buf, &i, "not out of time") ||
3044 looking_at(buf, &i, "either player is out of time") ||
3045 looking_at(buf, &i, "has timeseal; checking")) {
3046 /* We must have called his flag a little too soon */
3047 whiteFlag = blackFlag = FALSE;
3051 if (looking_at(buf, &i, "added * seconds to") ||
3052 looking_at(buf, &i, "seconds were added to")) {
3053 /* Update the clocks */
3054 SendToICS(ics_prefix);
3055 SendToICS("refresh\n");
3059 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3060 ics_clock_paused = TRUE;
3065 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3066 ics_clock_paused = FALSE;
3071 /* Grab player ratings from the Creating: message.
3072 Note we have to check for the special case when
3073 the ICS inserts things like [white] or [black]. */
3074 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3075 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077 0 player 1 name (not necessarily white)
3079 2 empty, white, or black (IGNORED)
3080 3 player 2 name (not necessarily black)
3083 The names/ratings are sorted out when the game
3084 actually starts (below).
3086 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3087 player1Rating = string_to_rating(star_match[1]);
3088 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3089 player2Rating = string_to_rating(star_match[4]);
3091 if (appData.debugMode)
3093 "Ratings from 'Creating:' %s %d, %s %d\n",
3094 player1Name, player1Rating,
3095 player2Name, player2Rating);
3100 /* Improved generic start/end-of-game messages */
3101 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3102 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3103 /* If tkind == 0: */
3104 /* star_match[0] is the game number */
3105 /* [1] is the white player's name */
3106 /* [2] is the black player's name */
3107 /* For end-of-game: */
3108 /* [3] is the reason for the game end */
3109 /* [4] is a PGN end game-token, preceded by " " */
3110 /* For start-of-game: */
3111 /* [3] begins with "Creating" or "Continuing" */
3112 /* [4] is " *" or empty (don't care). */
3113 int gamenum = atoi(star_match[0]);
3114 char *whitename, *blackname, *why, *endtoken;
3115 ChessMove endtype = (ChessMove) 0;
3118 whitename = star_match[1];
3119 blackname = star_match[2];
3120 why = star_match[3];
3121 endtoken = star_match[4];
3123 whitename = star_match[1];
3124 blackname = star_match[3];
3125 why = star_match[5];
3126 endtoken = star_match[6];
3129 /* Game start messages */
3130 if (strncmp(why, "Creating ", 9) == 0 ||
3131 strncmp(why, "Continuing ", 11) == 0) {
3132 gs_gamenum = gamenum;
3133 strcpy(gs_kind, strchr(why, ' ') + 1);
3135 if (appData.zippyPlay) {
3136 ZippyGameStart(whitename, blackname);
3142 /* Game end messages */
3143 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3144 ics_gamenum != gamenum) {
3147 while (endtoken[0] == ' ') endtoken++;
3148 switch (endtoken[0]) {
3151 endtype = GameUnfinished;
3154 endtype = BlackWins;
3157 if (endtoken[1] == '/')
3158 endtype = GameIsDrawn;
3160 endtype = WhiteWins;
3163 GameEnds(endtype, why, GE_ICS);
3165 if (appData.zippyPlay && first.initDone) {
3166 ZippyGameEnd(endtype, why);
3167 if (first.pr == NULL) {
3168 /* Start the next process early so that we'll
3169 be ready for the next challenge */
3170 StartChessProgram(&first);
3172 /* Send "new" early, in case this command takes
3173 a long time to finish, so that we'll be ready
3174 for the next challenge. */
3175 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3182 if (looking_at(buf, &i, "Removing game * from observation") ||
3183 looking_at(buf, &i, "no longer observing game *") ||
3184 looking_at(buf, &i, "Game * (*) has no examiners")) {
3185 if (gameMode == IcsObserving &&
3186 atoi(star_match[0]) == ics_gamenum)
3188 /* icsEngineAnalyze */
3189 if (appData.icsEngineAnalyze) {
3196 ics_user_moved = FALSE;
3201 if (looking_at(buf, &i, "no longer examining game *")) {
3202 if (gameMode == IcsExamining &&
3203 atoi(star_match[0]) == ics_gamenum)
3207 ics_user_moved = FALSE;
3212 /* Advance leftover_start past any newlines we find,
3213 so only partial lines can get reparsed */
3214 if (looking_at(buf, &i, "\n")) {
3215 prevColor = curColor;
3216 if (curColor != ColorNormal) {
3217 if (oldi > next_out) {
3218 SendToPlayer(&buf[next_out], oldi - next_out);
3221 Colorize(ColorNormal, FALSE);
3222 curColor = ColorNormal;
3224 if (started == STARTED_BOARD) {
3225 started = STARTED_NONE;
3226 parse[parse_pos] = NULLCHAR;
3227 ParseBoard12(parse);
3230 /* Send premove here */
3231 if (appData.premove) {
3233 if (currentMove == 0 &&
3234 gameMode == IcsPlayingWhite &&
3235 appData.premoveWhite) {
3236 sprintf(str, "%s%s\n", ics_prefix,
3237 appData.premoveWhiteText);
3238 if (appData.debugMode)
3239 fprintf(debugFP, "Sending premove:\n");
3241 } else if (currentMove == 1 &&
3242 gameMode == IcsPlayingBlack &&
3243 appData.premoveBlack) {
3244 sprintf(str, "%s%s\n", ics_prefix,
3245 appData.premoveBlackText);
3246 if (appData.debugMode)
3247 fprintf(debugFP, "Sending premove:\n");
3249 } else if (gotPremove) {
3251 ClearPremoveHighlights();
3252 if (appData.debugMode)
3253 fprintf(debugFP, "Sending premove:\n");
3254 UserMoveEvent(premoveFromX, premoveFromY,
3255 premoveToX, premoveToY,
3260 /* Usually suppress following prompt */
3261 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3262 if (looking_at(buf, &i, "*% ")) {
3263 savingComment = FALSE;
3267 } else if (started == STARTED_HOLDINGS) {
3269 char new_piece[MSG_SIZ];
3270 started = STARTED_NONE;
3271 parse[parse_pos] = NULLCHAR;
3272 if (appData.debugMode)
3273 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274 parse, currentMove);
3275 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276 gamenum == ics_gamenum) {
3277 if (gameInfo.variant == VariantNormal) {
3278 /* [HGM] We seem to switch variant during a game!
3279 * Presumably no holdings were displayed, so we have
3280 * to move the position two files to the right to
3281 * create room for them!
3283 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3284 /* Get a move list just to see the header, which
3285 will tell us whether this is really bug or zh */
3286 if (ics_getting_history == H_FALSE) {
3287 ics_getting_history = H_REQUESTED;
3288 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3292 new_piece[0] = NULLCHAR;
3293 sscanf(parse, "game %d white [%s black [%s <- %s",
3294 &gamenum, white_holding, black_holding,
3296 white_holding[strlen(white_holding)-1] = NULLCHAR;
3297 black_holding[strlen(black_holding)-1] = NULLCHAR;
3298 /* [HGM] copy holdings to board holdings area */
3299 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3300 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3302 if (appData.zippyPlay && first.initDone) {
3303 ZippyHoldings(white_holding, black_holding,
3307 if (tinyLayout || smallLayout) {
3308 char wh[16], bh[16];
3309 PackHolding(wh, white_holding);
3310 PackHolding(bh, black_holding);
3311 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3312 gameInfo.white, gameInfo.black);
3314 sprintf(str, "%s [%s] vs. %s [%s]",
3315 gameInfo.white, white_holding,
3316 gameInfo.black, black_holding);
3319 DrawPosition(FALSE, boards[currentMove]);
3322 /* Suppress following prompt */
3323 if (looking_at(buf, &i, "*% ")) {
3324 savingComment = FALSE;
3331 i++; /* skip unparsed character and loop back */
3334 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3335 started != STARTED_HOLDINGS && i > next_out) {
3336 SendToPlayer(&buf[next_out], i - next_out);
3339 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3341 leftover_len = buf_len - leftover_start;
3342 /* if buffer ends with something we couldn't parse,
3343 reparse it after appending the next read */
3345 } else if (count == 0) {
3346 RemoveInputSource(isr);
3347 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3349 DisplayFatalError(_("Error reading from ICS"), error, 1);
3354 /* Board style 12 looks like this:
3356 <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
3358 * The "<12> " is stripped before it gets to this routine. The two
3359 * trailing 0's (flip state and clock ticking) are later addition, and
3360 * some chess servers may not have them, or may have only the first.
3361 * Additional trailing fields may be added in the future.
3364 #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"
3366 #define RELATION_OBSERVING_PLAYED 0
3367 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3368 #define RELATION_PLAYING_MYMOVE 1
3369 #define RELATION_PLAYING_NOTMYMOVE -1
3370 #define RELATION_EXAMINING 2
3371 #define RELATION_ISOLATED_BOARD -3
3372 #define RELATION_STARTING_POSITION -4 /* FICS only */
3375 ParseBoard12(string)
3378 GameMode newGameMode;
3379 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3380 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3381 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3382 char to_play, board_chars[200];
3383 char move_str[500], str[500], elapsed_time[500];
3384 char black[32], white[32];
3386 int prevMove = currentMove;
3389 int fromX, fromY, toX, toY;
3391 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3392 char *bookHit = NULL; // [HGM] book
3394 fromX = fromY = toX = toY = -1;
3398 if (appData.debugMode)
3399 fprintf(debugFP, _("Parsing board: %s\n"), string);
3401 move_str[0] = NULLCHAR;
3402 elapsed_time[0] = NULLCHAR;
3403 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3405 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3406 if(string[i] == ' ') { ranks++; files = 0; }
3410 for(j = 0; j <i; j++) board_chars[j] = string[j];
3411 board_chars[i] = '\0';
3414 n = sscanf(string, PATTERN, &to_play, &double_push,
3415 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3416 &gamenum, white, black, &relation, &basetime, &increment,
3417 &white_stren, &black_stren, &white_time, &black_time,
3418 &moveNum, str, elapsed_time, move_str, &ics_flip,
3422 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3423 DisplayError(str, 0);
3427 /* Convert the move number to internal form */
3428 moveNum = (moveNum - 1) * 2;
3429 if (to_play == 'B') moveNum++;
3430 if (moveNum >= MAX_MOVES) {
3431 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3437 case RELATION_OBSERVING_PLAYED:
3438 case RELATION_OBSERVING_STATIC:
3439 if (gamenum == -1) {
3440 /* Old ICC buglet */
3441 relation = RELATION_OBSERVING_STATIC;
3443 newGameMode = IcsObserving;
3445 case RELATION_PLAYING_MYMOVE:
3446 case RELATION_PLAYING_NOTMYMOVE:
3448 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3449 IcsPlayingWhite : IcsPlayingBlack;
3451 case RELATION_EXAMINING:
3452 newGameMode = IcsExamining;
3454 case RELATION_ISOLATED_BOARD:
3456 /* Just display this board. If user was doing something else,
3457 we will forget about it until the next board comes. */
3458 newGameMode = IcsIdle;
3460 case RELATION_STARTING_POSITION:
3461 newGameMode = gameMode;
3465 /* Modify behavior for initial board display on move listing
3468 switch (ics_getting_history) {
3472 case H_GOT_REQ_HEADER:
3473 case H_GOT_UNREQ_HEADER:
3474 /* This is the initial position of the current game */
3475 gamenum = ics_gamenum;
3476 moveNum = 0; /* old ICS bug workaround */
3477 if (to_play == 'B') {
3478 startedFromSetupPosition = TRUE;
3479 blackPlaysFirst = TRUE;
3481 if (forwardMostMove == 0) forwardMostMove = 1;
3482 if (backwardMostMove == 0) backwardMostMove = 1;
3483 if (currentMove == 0) currentMove = 1;
3485 newGameMode = gameMode;
3486 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3488 case H_GOT_UNWANTED_HEADER:
3489 /* This is an initial board that we don't want */
3491 case H_GETTING_MOVES:
3492 /* Should not happen */
3493 DisplayError(_("Error gathering move list: extra board"), 0);
3494 ics_getting_history = H_FALSE;
3498 /* Take action if this is the first board of a new game, or of a
3499 different game than is currently being displayed. */
3500 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3501 relation == RELATION_ISOLATED_BOARD) {
3503 /* Forget the old game and get the history (if any) of the new one */
3504 if (gameMode != BeginningOfGame) {
3508 if (appData.autoRaiseBoard) BoardToTop();
3510 if (gamenum == -1) {
3511 newGameMode = IcsIdle;
3512 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3513 appData.getMoveList) {
3514 /* Need to get game history */
3515 ics_getting_history = H_REQUESTED;
3516 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3520 /* Initially flip the board to have black on the bottom if playing
3521 black or if the ICS flip flag is set, but let the user change
3522 it with the Flip View button. */
3523 flipView = appData.autoFlipView ?
3524 (newGameMode == IcsPlayingBlack) || ics_flip :
3527 /* Done with values from previous mode; copy in new ones */
3528 gameMode = newGameMode;
3530 ics_gamenum = gamenum;
3531 if (gamenum == gs_gamenum) {
3532 int klen = strlen(gs_kind);
3533 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3534 sprintf(str, "ICS %s", gs_kind);
3535 gameInfo.event = StrSave(str);
3537 gameInfo.event = StrSave("ICS game");
3539 gameInfo.site = StrSave(appData.icsHost);
3540 gameInfo.date = PGNDate();
3541 gameInfo.round = StrSave("-");
3542 gameInfo.white = StrSave(white);
3543 gameInfo.black = StrSave(black);
3544 timeControl = basetime * 60 * 1000;
3546 timeIncrement = increment * 1000;
3547 movesPerSession = 0;
3548 gameInfo.timeControl = TimeControlTagValue();
3549 VariantSwitch(board, StringToVariant(gameInfo.event) );
3550 if (appData.debugMode) {
3551 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3552 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3553 setbuf(debugFP, NULL);
3556 gameInfo.outOfBook = NULL;
3558 /* Do we have the ratings? */
3559 if (strcmp(player1Name, white) == 0 &&
3560 strcmp(player2Name, black) == 0) {
3561 if (appData.debugMode)
3562 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3563 player1Rating, player2Rating);
3564 gameInfo.whiteRating = player1Rating;
3565 gameInfo.blackRating = player2Rating;
3566 } else if (strcmp(player2Name, white) == 0 &&
3567 strcmp(player1Name, black) == 0) {
3568 if (appData.debugMode)
3569 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3570 player2Rating, player1Rating);
3571 gameInfo.whiteRating = player2Rating;
3572 gameInfo.blackRating = player1Rating;
3574 player1Name[0] = player2Name[0] = NULLCHAR;
3576 /* Silence shouts if requested */
3577 if (appData.quietPlay &&
3578 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3579 SendToICS(ics_prefix);
3580 SendToICS("set shout 0\n");
3584 /* Deal with midgame name changes */
3586 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3587 if (gameInfo.white) free(gameInfo.white);
3588 gameInfo.white = StrSave(white);
3590 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3591 if (gameInfo.black) free(gameInfo.black);
3592 gameInfo.black = StrSave(black);
3596 /* Throw away game result if anything actually changes in examine mode */
3597 if (gameMode == IcsExamining && !newGame) {
3598 gameInfo.result = GameUnfinished;
3599 if (gameInfo.resultDetails != NULL) {
3600 free(gameInfo.resultDetails);
3601 gameInfo.resultDetails = NULL;
3605 /* In pausing && IcsExamining mode, we ignore boards coming
3606 in if they are in a different variation than we are. */
3607 if (pauseExamInvalid) return;
3608 if (pausing && gameMode == IcsExamining) {
3609 if (moveNum <= pauseExamForwardMostMove) {
3610 pauseExamInvalid = TRUE;
3611 forwardMostMove = pauseExamForwardMostMove;
3616 if (appData.debugMode) {
3617 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3619 /* Parse the board */
3620 for (k = 0; k < ranks; k++) {
3621 for (j = 0; j < files; j++)
3622 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3623 if(gameInfo.holdingsWidth > 1) {
3624 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3625 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3628 CopyBoard(boards[moveNum], board);
3630 startedFromSetupPosition =
3631 !CompareBoards(board, initialPosition);
3632 if(startedFromSetupPosition)
3633 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3636 /* [HGM] Set castling rights. Take the outermost Rooks,
3637 to make it also work for FRC opening positions. Note that board12
3638 is really defective for later FRC positions, as it has no way to
3639 indicate which Rook can castle if they are on the same side of King.
3640 For the initial position we grant rights to the outermost Rooks,
3641 and remember thos rights, and we then copy them on positions
3642 later in an FRC game. This means WB might not recognize castlings with
3643 Rooks that have moved back to their original position as illegal,
3644 but in ICS mode that is not its job anyway.
3646 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3647 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3649 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3650 if(board[0][i] == WhiteRook) j = i;
3651 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3652 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3653 if(board[0][i] == WhiteRook) j = i;
3654 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3655 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3656 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3657 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3658 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3659 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3660 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3662 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3663 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3664 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3665 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3666 if(board[BOARD_HEIGHT-1][k] == bKing)
3667 initialRights[5] = castlingRights[moveNum][5] = k;
3669 r = castlingRights[moveNum][0] = initialRights[0];
3670 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3671 r = castlingRights[moveNum][1] = initialRights[1];
3672 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3673 r = castlingRights[moveNum][3] = initialRights[3];
3674 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3675 r = castlingRights[moveNum][4] = initialRights[4];
3676 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3677 /* wildcastle kludge: always assume King has rights */
3678 r = castlingRights[moveNum][2] = initialRights[2];
3679 r = castlingRights[moveNum][5] = initialRights[5];
3681 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3682 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3685 if (ics_getting_history == H_GOT_REQ_HEADER ||
3686 ics_getting_history == H_GOT_UNREQ_HEADER) {
3687 /* This was an initial position from a move list, not
3688 the current position */
3692 /* Update currentMove and known move number limits */
3693 newMove = newGame || moveNum > forwardMostMove;
3695 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3696 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3697 takeback = forwardMostMove - moveNum;
3698 for (i = 0; i < takeback; i++) {
3699 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3700 SendToProgram("undo\n", &first);
3705 forwardMostMove = backwardMostMove = currentMove = moveNum;
3706 if (gameMode == IcsExamining && moveNum == 0) {
3707 /* Workaround for ICS limitation: we are not told the wild
3708 type when starting to examine a game. But if we ask for
3709 the move list, the move list header will tell us */
3710 ics_getting_history = H_REQUESTED;
3711 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3714 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3715 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3716 forwardMostMove = moveNum;
3717 if (!pausing || currentMove > forwardMostMove)
3718 currentMove = forwardMostMove;
3720 /* New part of history that is not contiguous with old part */
3721 if (pausing && gameMode == IcsExamining) {
3722 pauseExamInvalid = TRUE;
3723 forwardMostMove = pauseExamForwardMostMove;
3726 forwardMostMove = backwardMostMove = currentMove = moveNum;
3727 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3728 ics_getting_history = H_REQUESTED;
3729 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3734 /* Update the clocks */
3735 if (strchr(elapsed_time, '.')) {
3737 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3738 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3740 /* Time is in seconds */
3741 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3742 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3747 if (appData.zippyPlay && newGame &&
3748 gameMode != IcsObserving && gameMode != IcsIdle &&
3749 gameMode != IcsExamining)
3750 ZippyFirstBoard(moveNum, basetime, increment);
3753 /* Put the move on the move list, first converting
3754 to canonical algebraic form. */
3756 if (appData.debugMode) {
3757 if (appData.debugMode) { int f = forwardMostMove;
3758 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3759 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3761 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3762 fprintf(debugFP, "moveNum = %d\n", moveNum);
3763 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3764 setbuf(debugFP, NULL);
3766 if (moveNum <= backwardMostMove) {
3767 /* We don't know what the board looked like before
3769 strcpy(parseList[moveNum - 1], move_str);
3770 strcat(parseList[moveNum - 1], " ");
3771 strcat(parseList[moveNum - 1], elapsed_time);
3772 moveList[moveNum - 1][0] = NULLCHAR;
3773 } else if (strcmp(move_str, "none") == 0) {
3774 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3775 /* Again, we don't know what the board looked like;
3776 this is really the start of the game. */
3777 parseList[moveNum - 1][0] = NULLCHAR;
3778 moveList[moveNum - 1][0] = NULLCHAR;
3779 backwardMostMove = moveNum;
3780 startedFromSetupPosition = TRUE;
3781 fromX = fromY = toX = toY = -1;
3783 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3784 // So we parse the long-algebraic move string in stead of the SAN move
3785 int valid; char buf[MSG_SIZ], *prom;
3787 // str looks something like "Q/a1-a2"; kill the slash
3789 sprintf(buf, "%c%s", str[0], str+2);
3790 else strcpy(buf, str); // might be castling
3791 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3792 strcat(buf, prom); // long move lacks promo specification!
3793 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3794 if(appData.debugMode)
3795 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3796 strcpy(move_str, buf);
3798 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3799 &fromX, &fromY, &toX, &toY, &promoChar)
3800 || ParseOneMove(buf, moveNum - 1, &moveType,
3801 &fromX, &fromY, &toX, &toY, &promoChar);
3802 // end of long SAN patch
3804 (void) CoordsToAlgebraic(boards[moveNum - 1],
3805 PosFlags(moveNum - 1), EP_UNKNOWN,
3806 fromY, fromX, toY, toX, promoChar,
3807 parseList[moveNum-1]);
3808 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3809 castlingRights[moveNum]) ) {
3815 if(gameInfo.variant != VariantShogi)
3816 strcat(parseList[moveNum - 1], "+");
3819 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3820 strcat(parseList[moveNum - 1], "#");
3823 strcat(parseList[moveNum - 1], " ");
3824 strcat(parseList[moveNum - 1], elapsed_time);
3825 /* currentMoveString is set as a side-effect of ParseOneMove */
3826 strcpy(moveList[moveNum - 1], currentMoveString);
3827 strcat(moveList[moveNum - 1], "\n");
3829 /* Move from ICS was illegal!? Punt. */
3830 if (appData.debugMode) {
3831 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);