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, 2010, 2011 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)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy( char *dst, const char *src, size_t count )
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble(u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
386 flags &= ~F_ALL_CASTLE_OK;
394 FILE *gameFileFP, *debugFP;
397 [AS] Note: sometimes, the sscanf() function is used to parse the input
398 into a fixed-size buffer. Because of this, we must be prepared to
399 receive strings as long as the size of the input buffer, which is currently
400 set to 4K for Windows and 8K for the rest.
401 So, we must either allocate sufficiently large buffers here, or
402 reduce the size of the input buffer in the input reading part.
405 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
406 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
407 char thinkOutput1[MSG_SIZ*10];
409 ChessProgramState first, second;
411 /* premove variables */
414 int premoveFromX = 0;
415 int premoveFromY = 0;
416 int premovePromoChar = 0;
418 Boolean alarmSounded;
419 /* end premove variables */
421 char *ics_prefix = "$";
422 int ics_type = ICS_GENERIC;
424 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
425 int pauseExamForwardMostMove = 0;
426 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
427 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
428 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
429 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
430 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
431 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
432 int whiteFlag = FALSE, blackFlag = FALSE;
433 int userOfferedDraw = FALSE;
434 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
435 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
436 int cmailMoveType[CMAIL_MAX_GAMES];
437 long ics_clock_paused = 0;
438 ProcRef icsPR = NoProc, cmailPR = NoProc;
439 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
440 GameMode gameMode = BeginningOfGame;
441 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
442 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
443 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
444 int hiddenThinkOutputState = 0; /* [AS] */
445 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
446 int adjudicateLossPlies = 6;
447 char white_holding[64], black_holding[64];
448 TimeMark lastNodeCountTime;
449 long lastNodeCount=0;
450 int shiftKey; // [HGM] set by mouse handler
452 int have_sent_ICS_logon = 0;
454 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
455 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
456 long timeControl_2; /* [AS] Allow separate time controls */
457 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
458 long timeRemaining[2][MAX_MOVES];
460 TimeMark programStartTime;
461 char ics_handle[MSG_SIZ];
462 int have_set_title = 0;
464 /* animateTraining preserves the state of appData.animate
465 * when Training mode is activated. This allows the
466 * response to be animated when appData.animate == TRUE and
467 * appData.animateDragging == TRUE.
469 Boolean animateTraining;
475 Board boards[MAX_MOVES];
476 /* [HGM] Following 7 needed for accurate legality tests: */
477 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
478 signed char initialRights[BOARD_FILES];
479 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
480 int initialRulePlies, FENrulePlies;
481 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
484 int mute; // mute all sounds
486 // [HGM] vari: next 12 to save and restore variations
487 #define MAX_VARIATIONS 10
488 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int savedFirst[MAX_VARIATIONS];
491 int savedLast[MAX_VARIATIONS];
492 int savedFramePtr[MAX_VARIATIONS];
493 char *savedDetails[MAX_VARIATIONS];
494 ChessMove savedResult[MAX_VARIATIONS];
496 void PushTail P((int firstMove, int lastMove));
497 Boolean PopTail P((Boolean annotate));
498 void CleanupTail P((void));
500 ChessSquare FIDEArray[2][BOARD_FILES] = {
501 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
502 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
504 BlackKing, BlackBishop, BlackKnight, BlackRook }
507 ChessSquare twoKingsArray[2][BOARD_FILES] = {
508 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511 BlackKing, BlackKing, BlackKnight, BlackRook }
514 ChessSquare KnightmateArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
516 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
517 { BlackRook, BlackMan, BlackBishop, BlackQueen,
518 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
521 ChessSquare SpartanArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
524 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
525 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
528 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
529 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
532 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
535 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
536 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
537 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
538 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
539 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
542 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
544 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackMan, BlackFerz,
546 BlackKing, BlackMan, BlackKnight, BlackRook }
550 #if (BOARD_FILES>=10)
551 ChessSquare ShogiArray[2][BOARD_FILES] = {
552 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
553 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
554 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
555 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
558 ChessSquare XiangqiArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
560 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
562 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
565 ChessSquare CapablancaArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
567 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
569 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
572 ChessSquare GreatArray[2][BOARD_FILES] = {
573 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
574 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
575 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
576 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
579 ChessSquare JanusArray[2][BOARD_FILES] = {
580 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
581 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
582 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
583 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
587 ChessSquare GothicArray[2][BOARD_FILES] = {
588 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
589 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
590 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
591 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
594 #define GothicArray CapablancaArray
598 ChessSquare FalconArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
600 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
601 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
602 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
605 #define FalconArray CapablancaArray
608 #else // !(BOARD_FILES>=10)
609 #define XiangqiPosition FIDEArray
610 #define CapablancaArray FIDEArray
611 #define GothicArray FIDEArray
612 #define GreatArray FIDEArray
613 #endif // !(BOARD_FILES>=10)
615 #if (BOARD_FILES>=12)
616 ChessSquare CourierArray[2][BOARD_FILES] = {
617 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
618 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
619 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
620 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
622 #else // !(BOARD_FILES>=12)
623 #define CourierArray CapablancaArray
624 #endif // !(BOARD_FILES>=12)
627 Board initialPosition;
630 /* Convert str to a rating. Checks for special cases of "----",
632 "++++", etc. Also strips ()'s */
634 string_to_rating(str)
637 while(*str && !isdigit(*str)) ++str;
639 return 0; /* One of the special "no rating" cases */
647 /* Init programStats */
648 programStats.movelist[0] = 0;
649 programStats.depth = 0;
650 programStats.nr_moves = 0;
651 programStats.moves_left = 0;
652 programStats.nodes = 0;
653 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
654 programStats.score = 0;
655 programStats.got_only_move = 0;
656 programStats.got_fail = 0;
657 programStats.line_is_book = 0;
663 int matched, min, sec;
665 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
666 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
668 GetTimeMark(&programStartTime);
669 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
672 programStats.ok_to_send = 1;
673 programStats.seen_stat = 0;
676 * Initialize game list
682 * Internet chess server status
684 if (appData.icsActive) {
685 appData.matchMode = FALSE;
686 appData.matchGames = 0;
688 appData.noChessProgram = !appData.zippyPlay;
690 appData.zippyPlay = FALSE;
691 appData.zippyTalk = FALSE;
692 appData.noChessProgram = TRUE;
694 if (*appData.icsHelper != NULLCHAR) {
695 appData.useTelnet = TRUE;
696 appData.telnetProgram = appData.icsHelper;
699 appData.zippyTalk = appData.zippyPlay = FALSE;
702 /* [AS] Initialize pv info list [HGM] and game state */
706 for( i=0; i<=framePtr; i++ ) {
707 pvInfoList[i].depth = -1;
708 boards[i][EP_STATUS] = EP_NONE;
709 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
714 * Parse timeControl resource
716 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
717 appData.movesPerSession)) {
719 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
720 DisplayFatalError(buf, 0, 2);
724 * Parse searchTime resource
726 if (*appData.searchTime != NULLCHAR) {
727 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
729 searchTime = min * 60;
730 } else if (matched == 2) {
731 searchTime = min * 60 + sec;
734 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
735 DisplayFatalError(buf, 0, 2);
739 /* [AS] Adjudication threshold */
740 adjudicateLossThreshold = appData.adjudicateLossThreshold;
742 first.which = "first";
743 second.which = "second";
744 first.maybeThinking = second.maybeThinking = FALSE;
745 first.pr = second.pr = NoProc;
746 first.isr = second.isr = NULL;
747 first.sendTime = second.sendTime = 2;
748 first.sendDrawOffers = 1;
749 if (appData.firstPlaysBlack) {
750 first.twoMachinesColor = "black\n";
751 second.twoMachinesColor = "white\n";
753 first.twoMachinesColor = "white\n";
754 second.twoMachinesColor = "black\n";
756 first.program = appData.firstChessProgram;
757 second.program = appData.secondChessProgram;
758 first.host = appData.firstHost;
759 second.host = appData.secondHost;
760 first.dir = appData.firstDirectory;
761 second.dir = appData.secondDirectory;
762 first.other = &second;
763 second.other = &first;
764 first.initString = appData.initString;
765 second.initString = appData.secondInitString;
766 first.computerString = appData.firstComputerString;
767 second.computerString = appData.secondComputerString;
768 first.useSigint = second.useSigint = TRUE;
769 first.useSigterm = second.useSigterm = TRUE;
770 first.reuse = appData.reuseFirst;
771 second.reuse = appData.reuseSecond;
772 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
773 second.nps = appData.secondNPS;
774 first.useSetboard = second.useSetboard = FALSE;
775 first.useSAN = second.useSAN = FALSE;
776 first.usePing = second.usePing = FALSE;
777 first.lastPing = second.lastPing = 0;
778 first.lastPong = second.lastPong = 0;
779 first.usePlayother = second.usePlayother = FALSE;
780 first.useColors = second.useColors = TRUE;
781 first.useUsermove = second.useUsermove = FALSE;
782 first.sendICS = second.sendICS = FALSE;
783 first.sendName = second.sendName = appData.icsActive;
784 first.sdKludge = second.sdKludge = FALSE;
785 first.stKludge = second.stKludge = FALSE;
786 TidyProgramName(first.program, first.host, first.tidy);
787 TidyProgramName(second.program, second.host, second.tidy);
788 first.matchWins = second.matchWins = 0;
789 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
790 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
791 first.analysisSupport = second.analysisSupport = 2; /* detect */
792 first.analyzing = second.analyzing = FALSE;
793 first.initDone = second.initDone = FALSE;
795 /* New features added by Tord: */
796 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
797 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
798 /* End of new features added by Tord. */
799 first.fenOverride = appData.fenOverride1;
800 second.fenOverride = appData.fenOverride2;
802 /* [HGM] time odds: set factor for each machine */
803 first.timeOdds = appData.firstTimeOdds;
804 second.timeOdds = appData.secondTimeOdds;
806 if(appData.timeOddsMode) {
807 norm = first.timeOdds;
808 if(norm > second.timeOdds) norm = second.timeOdds;
810 first.timeOdds /= norm;
811 second.timeOdds /= norm;
814 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
815 first.accumulateTC = appData.firstAccumulateTC;
816 second.accumulateTC = appData.secondAccumulateTC;
817 first.maxNrOfSessions = second.maxNrOfSessions = 1;
820 first.debug = second.debug = FALSE;
821 first.supportsNPS = second.supportsNPS = UNKNOWN;
824 first.optionSettings = appData.firstOptions;
825 second.optionSettings = appData.secondOptions;
827 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
828 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
829 first.isUCI = appData.firstIsUCI; /* [AS] */
830 second.isUCI = appData.secondIsUCI; /* [AS] */
831 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
832 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
834 if (appData.firstProtocolVersion > PROTOVER
835 || appData.firstProtocolVersion < 1)
840 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
841 appData.firstProtocolVersion);
842 if( (len > MSG_SIZ) && appData.debugMode )
843 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
845 DisplayFatalError(buf, 0, 2);
849 first.protocolVersion = appData.firstProtocolVersion;
852 if (appData.secondProtocolVersion > PROTOVER
853 || appData.secondProtocolVersion < 1)
858 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
859 appData.secondProtocolVersion);
860 if( (len > MSG_SIZ) && appData.debugMode )
861 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863 DisplayFatalError(buf, 0, 2);
867 second.protocolVersion = appData.secondProtocolVersion;
870 if (appData.icsActive) {
871 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
872 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
873 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
874 appData.clockMode = FALSE;
875 first.sendTime = second.sendTime = 0;
879 /* Override some settings from environment variables, for backward
880 compatibility. Unfortunately it's not feasible to have the env
881 vars just set defaults, at least in xboard. Ugh.
883 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
888 if (appData.noChessProgram) {
889 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
890 sprintf(programVersion, "%s", PACKAGE_STRING);
892 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
893 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
894 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
897 if (!appData.icsActive) {
901 /* Check for variants that are supported only in ICS mode,
902 or not at all. Some that are accepted here nevertheless
903 have bugs; see comments below.
905 VariantClass variant = StringToVariant(appData.variant);
907 case VariantBughouse: /* need four players and two boards */
908 case VariantKriegspiel: /* need to hide pieces and move details */
909 /* case VariantFischeRandom: (Fabien: moved below) */
910 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
911 if( (len > MSG_SIZ) && appData.debugMode )
912 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
914 DisplayFatalError(buf, 0, 2);
918 case VariantLoadable:
928 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
929 if( (len > MSG_SIZ) && appData.debugMode )
930 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
932 DisplayFatalError(buf, 0, 2);
935 case VariantXiangqi: /* [HGM] repetition rules not implemented */
936 case VariantFairy: /* [HGM] TestLegality definitely off! */
937 case VariantGothic: /* [HGM] should work */
938 case VariantCapablanca: /* [HGM] should work */
939 case VariantCourier: /* [HGM] initial forced moves not implemented */
940 case VariantShogi: /* [HGM] could still mate with pawn drop */
941 case VariantKnightmate: /* [HGM] should work */
942 case VariantCylinder: /* [HGM] untested */
943 case VariantFalcon: /* [HGM] untested */
944 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
945 offboard interposition not understood */
946 case VariantNormal: /* definitely works! */
947 case VariantWildCastle: /* pieces not automatically shuffled */
948 case VariantNoCastle: /* pieces not automatically shuffled */
949 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
950 case VariantLosers: /* should work except for win condition,
951 and doesn't know captures are mandatory */
952 case VariantSuicide: /* should work except for win condition,
953 and doesn't know captures are mandatory */
954 case VariantGiveaway: /* should work except for win condition,
955 and doesn't know captures are mandatory */
956 case VariantTwoKings: /* should work */
957 case VariantAtomic: /* should work except for win condition */
958 case Variant3Check: /* should work except for win condition */
959 case VariantShatranj: /* should work except for all win conditions */
960 case VariantMakruk: /* should work except for daw countdown */
961 case VariantBerolina: /* might work if TestLegality is off */
962 case VariantCapaRandom: /* should work */
963 case VariantJanus: /* should work */
964 case VariantSuper: /* experimental */
965 case VariantGreat: /* experimental, requires legality testing to be off */
966 case VariantSChess: /* S-Chess, should work */
967 case VariantSpartan: /* should work */
972 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
973 InitEngineUCI( installDir, &second );
976 int NextIntegerFromString( char ** str, long * value )
981 while( *s == ' ' || *s == '\t' ) {
987 if( *s >= '0' && *s <= '9' ) {
988 while( *s >= '0' && *s <= '9' ) {
989 *value = *value * 10 + (*s - '0');
1001 int NextTimeControlFromString( char ** str, long * value )
1004 int result = NextIntegerFromString( str, &temp );
1007 *value = temp * 60; /* Minutes */
1008 if( **str == ':' ) {
1010 result = NextIntegerFromString( str, &temp );
1011 *value += temp; /* Seconds */
1018 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1019 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1020 int result = -1, type = 0; long temp, temp2;
1022 if(**str != ':') return -1; // old params remain in force!
1024 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1025 if( NextIntegerFromString( str, &temp ) ) return -1;
1026 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1029 /* time only: incremental or sudden-death time control */
1030 if(**str == '+') { /* increment follows; read it */
1032 if(**str == '!') type = *(*str)++; // Bronstein TC
1033 if(result = NextIntegerFromString( str, &temp2)) return -1;
1034 *inc = temp2 * 1000;
1035 if(**str == '.') { // read fraction of increment
1036 char *start = ++(*str);
1037 if(result = NextIntegerFromString( str, &temp2)) return -1;
1039 while(start++ < *str) temp2 /= 10;
1043 *moves = 0; *tc = temp * 1000; *incType = type;
1047 (*str)++; /* classical time control */
1048 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1059 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1060 { /* [HGM] get time to add from the multi-session time-control string */
1061 int incType, moves=1; /* kludge to force reading of first session */
1062 long time, increment;
1065 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1066 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1068 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1069 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1070 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1071 if(movenr == -1) return time; /* last move before new session */
1072 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1073 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1074 if(!moves) return increment; /* current session is incremental */
1075 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1076 } while(movenr >= -1); /* try again for next session */
1078 return 0; // no new time quota on this move
1082 ParseTimeControl(tc, ti, mps)
1089 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1092 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1093 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1094 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1098 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1100 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1103 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1105 snprintf(buf, MSG_SIZ, ":%s", mytc);
1107 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1109 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1114 /* Parse second time control */
1117 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1125 timeControl_2 = tc2 * 1000;
1135 timeControl = tc1 * 1000;
1138 timeIncrement = ti * 1000; /* convert to ms */
1139 movesPerSession = 0;
1142 movesPerSession = mps;
1150 if (appData.debugMode) {
1151 fprintf(debugFP, "%s\n", programVersion);
1154 set_cont_sequence(appData.wrapContSeq);
1155 if (appData.matchGames > 0) {
1156 appData.matchMode = TRUE;
1157 } else if (appData.matchMode) {
1158 appData.matchGames = 1;
1160 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1161 appData.matchGames = appData.sameColorGames;
1162 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1163 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1164 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1167 if (appData.noChessProgram || first.protocolVersion == 1) {
1170 /* kludge: allow timeout for initial "feature" commands */
1172 DisplayMessage("", _("Starting chess program"));
1173 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1178 InitBackEnd3 P((void))
1180 GameMode initialMode;
1184 InitChessProgram(&first, startedFromSetupPosition);
1186 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1187 free(programVersion);
1188 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1189 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1192 if (appData.icsActive) {
1194 /* [DM] Make a console window if needed [HGM] merged ifs */
1200 if (*appData.icsCommPort != NULLCHAR)
1201 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1202 appData.icsCommPort);
1204 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1205 appData.icsHost, appData.icsPort);
1207 if( (len > MSG_SIZ) && appData.debugMode )
1208 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1210 DisplayFatalError(buf, err, 1);
1215 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1217 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1218 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1219 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1220 } else if (appData.noChessProgram) {
1226 if (*appData.cmailGameName != NULLCHAR) {
1228 OpenLoopback(&cmailPR);
1230 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1234 DisplayMessage("", "");
1235 if (StrCaseCmp(appData.initialMode, "") == 0) {
1236 initialMode = BeginningOfGame;
1237 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1238 initialMode = TwoMachinesPlay;
1239 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1240 initialMode = AnalyzeFile;
1241 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1242 initialMode = AnalyzeMode;
1243 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1244 initialMode = MachinePlaysWhite;
1245 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1246 initialMode = MachinePlaysBlack;
1247 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1248 initialMode = EditGame;
1249 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1250 initialMode = EditPosition;
1251 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1252 initialMode = Training;
1254 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1255 if( (len > MSG_SIZ) && appData.debugMode )
1256 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1258 DisplayFatalError(buf, 0, 2);
1262 if (appData.matchMode) {
1263 /* Set up machine vs. machine match */
1264 if (appData.noChessProgram) {
1265 DisplayFatalError(_("Can't have a match with no chess programs"),
1271 if (*appData.loadGameFile != NULLCHAR) {
1272 int index = appData.loadGameIndex; // [HGM] autoinc
1273 if(index<0) lastIndex = index = 1;
1274 if (!LoadGameFromFile(appData.loadGameFile,
1276 appData.loadGameFile, FALSE)) {
1277 DisplayFatalError(_("Bad game file"), 0, 1);
1280 } else if (*appData.loadPositionFile != NULLCHAR) {
1281 int index = appData.loadPositionIndex; // [HGM] autoinc
1282 if(index<0) lastIndex = index = 1;
1283 if (!LoadPositionFromFile(appData.loadPositionFile,
1285 appData.loadPositionFile)) {
1286 DisplayFatalError(_("Bad position file"), 0, 1);
1291 } else if (*appData.cmailGameName != NULLCHAR) {
1292 /* Set up cmail mode */
1293 ReloadCmailMsgEvent(TRUE);
1295 /* Set up other modes */
1296 if (initialMode == AnalyzeFile) {
1297 if (*appData.loadGameFile == NULLCHAR) {
1298 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1302 if (*appData.loadGameFile != NULLCHAR) {
1303 (void) LoadGameFromFile(appData.loadGameFile,
1304 appData.loadGameIndex,
1305 appData.loadGameFile, TRUE);
1306 } else if (*appData.loadPositionFile != NULLCHAR) {
1307 (void) LoadPositionFromFile(appData.loadPositionFile,
1308 appData.loadPositionIndex,
1309 appData.loadPositionFile);
1310 /* [HGM] try to make self-starting even after FEN load */
1311 /* to allow automatic setup of fairy variants with wtm */
1312 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1313 gameMode = BeginningOfGame;
1314 setboardSpoiledMachineBlack = 1;
1316 /* [HGM] loadPos: make that every new game uses the setup */
1317 /* from file as long as we do not switch variant */
1318 if(!blackPlaysFirst) {
1319 startedFromPositionFile = TRUE;
1320 CopyBoard(filePosition, boards[0]);
1323 if (initialMode == AnalyzeMode) {
1324 if (appData.noChessProgram) {
1325 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1328 if (appData.icsActive) {
1329 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1333 } else if (initialMode == AnalyzeFile) {
1334 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1335 ShowThinkingEvent();
1337 AnalysisPeriodicEvent(1);
1338 } else if (initialMode == MachinePlaysWhite) {
1339 if (appData.noChessProgram) {
1340 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1344 if (appData.icsActive) {
1345 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1349 MachineWhiteEvent();
1350 } else if (initialMode == MachinePlaysBlack) {
1351 if (appData.noChessProgram) {
1352 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1356 if (appData.icsActive) {
1357 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1361 MachineBlackEvent();
1362 } else if (initialMode == TwoMachinesPlay) {
1363 if (appData.noChessProgram) {
1364 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1368 if (appData.icsActive) {
1369 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1374 } else if (initialMode == EditGame) {
1376 } else if (initialMode == EditPosition) {
1377 EditPositionEvent();
1378 } else if (initialMode == Training) {
1379 if (*appData.loadGameFile == NULLCHAR) {
1380 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1389 * Establish will establish a contact to a remote host.port.
1390 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1391 * used to talk to the host.
1392 * Returns 0 if okay, error code if not.
1399 if (*appData.icsCommPort != NULLCHAR) {
1400 /* Talk to the host through a serial comm port */
1401 return OpenCommPort(appData.icsCommPort, &icsPR);
1403 } else if (*appData.gateway != NULLCHAR) {
1404 if (*appData.remoteShell == NULLCHAR) {
1405 /* Use the rcmd protocol to run telnet program on a gateway host */
1406 snprintf(buf, sizeof(buf), "%s %s %s",
1407 appData.telnetProgram, appData.icsHost, appData.icsPort);
1408 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1411 /* Use the rsh program to run telnet program on a gateway host */
1412 if (*appData.remoteUser == NULLCHAR) {
1413 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1414 appData.gateway, appData.telnetProgram,
1415 appData.icsHost, appData.icsPort);
1417 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1418 appData.remoteShell, appData.gateway,
1419 appData.remoteUser, appData.telnetProgram,
1420 appData.icsHost, appData.icsPort);
1422 return StartChildProcess(buf, "", &icsPR);
1425 } else if (appData.useTelnet) {
1426 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1429 /* TCP socket interface differs somewhat between
1430 Unix and NT; handle details in the front end.
1432 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1436 void EscapeExpand(char *p, char *q)
1437 { // [HGM] initstring: routine to shape up string arguments
1438 while(*p++ = *q++) if(p[-1] == '\\')
1440 case 'n': p[-1] = '\n'; break;
1441 case 'r': p[-1] = '\r'; break;
1442 case 't': p[-1] = '\t'; break;
1443 case '\\': p[-1] = '\\'; break;
1444 case 0: *p = 0; return;
1445 default: p[-1] = q[-1]; break;
1450 show_bytes(fp, buf, count)
1456 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1457 fprintf(fp, "\\%03o", *buf & 0xff);
1466 /* Returns an errno value */
1468 OutputMaybeTelnet(pr, message, count, outError)
1474 char buf[8192], *p, *q, *buflim;
1475 int left, newcount, outcount;
1477 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1478 *appData.gateway != NULLCHAR) {
1479 if (appData.debugMode) {
1480 fprintf(debugFP, ">ICS: ");
1481 show_bytes(debugFP, message, count);
1482 fprintf(debugFP, "\n");
1484 return OutputToProcess(pr, message, count, outError);
1487 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1494 if (appData.debugMode) {
1495 fprintf(debugFP, ">ICS: ");
1496 show_bytes(debugFP, buf, newcount);
1497 fprintf(debugFP, "\n");
1499 outcount = OutputToProcess(pr, buf, newcount, outError);
1500 if (outcount < newcount) return -1; /* to be sure */
1507 } else if (((unsigned char) *p) == TN_IAC) {
1508 *q++ = (char) TN_IAC;
1515 if (appData.debugMode) {
1516 fprintf(debugFP, ">ICS: ");
1517 show_bytes(debugFP, buf, newcount);
1518 fprintf(debugFP, "\n");
1520 outcount = OutputToProcess(pr, buf, newcount, outError);
1521 if (outcount < newcount) return -1; /* to be sure */
1526 read_from_player(isr, closure, message, count, error)
1533 int outError, outCount;
1534 static int gotEof = 0;
1536 /* Pass data read from player on to ICS */
1539 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1540 if (outCount < count) {
1541 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1543 } else if (count < 0) {
1544 RemoveInputSource(isr);
1545 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1546 } else if (gotEof++ > 0) {
1547 RemoveInputSource(isr);
1548 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1554 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1555 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1556 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1557 SendToICS("date\n");
1558 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1561 /* added routine for printf style output to ics */
1562 void ics_printf(char *format, ...)
1564 char buffer[MSG_SIZ];
1567 va_start(args, format);
1568 vsnprintf(buffer, sizeof(buffer), format, args);
1569 buffer[sizeof(buffer)-1] = '\0';
1578 int count, outCount, outError;
1580 if (icsPR == NULL) return;
1583 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1584 if (outCount < count) {
1585 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1589 /* This is used for sending logon scripts to the ICS. Sending
1590 without a delay causes problems when using timestamp on ICC
1591 (at least on my machine). */
1593 SendToICSDelayed(s,msdelay)
1597 int count, outCount, outError;
1599 if (icsPR == NULL) return;
1602 if (appData.debugMode) {
1603 fprintf(debugFP, ">ICS: ");
1604 show_bytes(debugFP, s, count);
1605 fprintf(debugFP, "\n");
1607 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1609 if (outCount < count) {
1610 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1615 /* Remove all highlighting escape sequences in s
1616 Also deletes any suffix starting with '('
1619 StripHighlightAndTitle(s)
1622 static char retbuf[MSG_SIZ];
1625 while (*s != NULLCHAR) {
1626 while (*s == '\033') {
1627 while (*s != NULLCHAR && !isalpha(*s)) s++;
1628 if (*s != NULLCHAR) s++;
1630 while (*s != NULLCHAR && *s != '\033') {
1631 if (*s == '(' || *s == '[') {
1642 /* Remove all highlighting escape sequences in s */
1647 static char retbuf[MSG_SIZ];
1650 while (*s != NULLCHAR) {
1651 while (*s == '\033') {
1652 while (*s != NULLCHAR && !isalpha(*s)) s++;
1653 if (*s != NULLCHAR) s++;
1655 while (*s != NULLCHAR && *s != '\033') {
1663 char *variantNames[] = VARIANT_NAMES;
1668 return variantNames[v];
1672 /* Identify a variant from the strings the chess servers use or the
1673 PGN Variant tag names we use. */
1680 VariantClass v = VariantNormal;
1681 int i, found = FALSE;
1687 /* [HGM] skip over optional board-size prefixes */
1688 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1689 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1690 while( *e++ != '_');
1693 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1697 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1698 if (StrCaseStr(e, variantNames[i])) {
1699 v = (VariantClass) i;
1706 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1707 || StrCaseStr(e, "wild/fr")
1708 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1709 v = VariantFischeRandom;
1710 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1711 (i = 1, p = StrCaseStr(e, "w"))) {
1713 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1720 case 0: /* FICS only, actually */
1722 /* Castling legal even if K starts on d-file */
1723 v = VariantWildCastle;
1728 /* Castling illegal even if K & R happen to start in
1729 normal positions. */
1730 v = VariantNoCastle;
1743 /* Castling legal iff K & R start in normal positions */
1749 /* Special wilds for position setup; unclear what to do here */
1750 v = VariantLoadable;
1753 /* Bizarre ICC game */
1754 v = VariantTwoKings;
1757 v = VariantKriegspiel;
1763 v = VariantFischeRandom;
1766 v = VariantCrazyhouse;
1769 v = VariantBughouse;
1775 /* Not quite the same as FICS suicide! */
1776 v = VariantGiveaway;
1782 v = VariantShatranj;
1785 /* Temporary names for future ICC types. The name *will* change in
1786 the next xboard/WinBoard release after ICC defines it. */
1824 v = VariantCapablanca;
1827 v = VariantKnightmate;
1833 v = VariantCylinder;
1839 v = VariantCapaRandom;
1842 v = VariantBerolina;
1854 /* Found "wild" or "w" in the string but no number;
1855 must assume it's normal chess. */
1859 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1860 if( (len > MSG_SIZ) && appData.debugMode )
1861 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1863 DisplayError(buf, 0);
1869 if (appData.debugMode) {
1870 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1871 e, wnum, VariantName(v));
1876 static int leftover_start = 0, leftover_len = 0;
1877 char star_match[STAR_MATCH_N][MSG_SIZ];
1879 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1880 advance *index beyond it, and set leftover_start to the new value of
1881 *index; else return FALSE. If pattern contains the character '*', it
1882 matches any sequence of characters not containing '\r', '\n', or the
1883 character following the '*' (if any), and the matched sequence(s) are
1884 copied into star_match.
1887 looking_at(buf, index, pattern)
1892 char *bufp = &buf[*index], *patternp = pattern;
1894 char *matchp = star_match[0];
1897 if (*patternp == NULLCHAR) {
1898 *index = leftover_start = bufp - buf;
1902 if (*bufp == NULLCHAR) return FALSE;
1903 if (*patternp == '*') {
1904 if (*bufp == *(patternp + 1)) {
1906 matchp = star_match[++star_count];
1910 } else if (*bufp == '\n' || *bufp == '\r') {
1912 if (*patternp == NULLCHAR)
1917 *matchp++ = *bufp++;
1921 if (*patternp != *bufp) return FALSE;
1928 SendToPlayer(data, length)
1932 int error, outCount;
1933 outCount = OutputToProcess(NoProc, data, length, &error);
1934 if (outCount < length) {
1935 DisplayFatalError(_("Error writing to display"), error, 1);
1940 PackHolding(packed, holding)
1952 switch (runlength) {
1963 sprintf(q, "%d", runlength);
1975 /* Telnet protocol requests from the front end */
1977 TelnetRequest(ddww, option)
1978 unsigned char ddww, option;
1980 unsigned char msg[3];
1981 int outCount, outError;
1983 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1985 if (appData.debugMode) {
1986 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2002 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2011 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2014 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2019 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2021 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2028 if (!appData.icsActive) return;
2029 TelnetRequest(TN_DO, TN_ECHO);
2035 if (!appData.icsActive) return;
2036 TelnetRequest(TN_DONT, TN_ECHO);
2040 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2042 /* put the holdings sent to us by the server on the board holdings area */
2043 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2047 if(gameInfo.holdingsWidth < 2) return;
2048 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2049 return; // prevent overwriting by pre-board holdings
2051 if( (int)lowestPiece >= BlackPawn ) {
2054 holdingsStartRow = BOARD_HEIGHT-1;
2057 holdingsColumn = BOARD_WIDTH-1;
2058 countsColumn = BOARD_WIDTH-2;
2059 holdingsStartRow = 0;
2063 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2064 board[i][holdingsColumn] = EmptySquare;
2065 board[i][countsColumn] = (ChessSquare) 0;
2067 while( (p=*holdings++) != NULLCHAR ) {
2068 piece = CharToPiece( ToUpper(p) );
2069 if(piece == EmptySquare) continue;
2070 /*j = (int) piece - (int) WhitePawn;*/
2071 j = PieceToNumber(piece);
2072 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2073 if(j < 0) continue; /* should not happen */
2074 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2075 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2076 board[holdingsStartRow+j*direction][countsColumn]++;
2082 VariantSwitch(Board board, VariantClass newVariant)
2084 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2085 static Board oldBoard;
2087 startedFromPositionFile = FALSE;
2088 if(gameInfo.variant == newVariant) return;
2090 /* [HGM] This routine is called each time an assignment is made to
2091 * gameInfo.variant during a game, to make sure the board sizes
2092 * are set to match the new variant. If that means adding or deleting
2093 * holdings, we shift the playing board accordingly
2094 * This kludge is needed because in ICS observe mode, we get boards
2095 * of an ongoing game without knowing the variant, and learn about the
2096 * latter only later. This can be because of the move list we requested,
2097 * in which case the game history is refilled from the beginning anyway,
2098 * but also when receiving holdings of a crazyhouse game. In the latter
2099 * case we want to add those holdings to the already received position.
2103 if (appData.debugMode) {
2104 fprintf(debugFP, "Switch board from %s to %s\n",
2105 VariantName(gameInfo.variant), VariantName(newVariant));
2106 setbuf(debugFP, NULL);
2108 shuffleOpenings = 0; /* [HGM] shuffle */
2109 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2113 newWidth = 9; newHeight = 9;
2114 gameInfo.holdingsSize = 7;
2115 case VariantBughouse:
2116 case VariantCrazyhouse:
2117 newHoldingsWidth = 2; break;
2121 newHoldingsWidth = 2;
2122 gameInfo.holdingsSize = 8;
2125 case VariantCapablanca:
2126 case VariantCapaRandom:
2129 newHoldingsWidth = gameInfo.holdingsSize = 0;
2132 if(newWidth != gameInfo.boardWidth ||
2133 newHeight != gameInfo.boardHeight ||
2134 newHoldingsWidth != gameInfo.holdingsWidth ) {
2136 /* shift position to new playing area, if needed */
2137 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2138 for(i=0; i<BOARD_HEIGHT; i++)
2139 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2140 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142 for(i=0; i<newHeight; i++) {
2143 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2144 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2146 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2147 for(i=0; i<BOARD_HEIGHT; i++)
2148 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2149 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2152 gameInfo.boardWidth = newWidth;
2153 gameInfo.boardHeight = newHeight;
2154 gameInfo.holdingsWidth = newHoldingsWidth;
2155 gameInfo.variant = newVariant;
2156 InitDrawingSizes(-2, 0);
2157 } else gameInfo.variant = newVariant;
2158 CopyBoard(oldBoard, board); // remember correctly formatted board
2159 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2160 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2163 static int loggedOn = FALSE;
2165 /*-- Game start info cache: --*/
2167 char gs_kind[MSG_SIZ];
2168 static char player1Name[128] = "";
2169 static char player2Name[128] = "";
2170 static char cont_seq[] = "\n\\ ";
2171 static int player1Rating = -1;
2172 static int player2Rating = -1;
2173 /*----------------------------*/
2175 ColorClass curColor = ColorNormal;
2176 int suppressKibitz = 0;
2179 Boolean soughtPending = FALSE;
2180 Boolean seekGraphUp;
2181 #define MAX_SEEK_ADS 200
2183 char *seekAdList[MAX_SEEK_ADS];
2184 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2185 float tcList[MAX_SEEK_ADS];
2186 char colorList[MAX_SEEK_ADS];
2187 int nrOfSeekAds = 0;
2188 int minRating = 1010, maxRating = 2800;
2189 int hMargin = 10, vMargin = 20, h, w;
2190 extern int squareSize, lineGap;
2195 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2196 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2197 if(r < minRating+100 && r >=0 ) r = minRating+100;
2198 if(r > maxRating) r = maxRating;
2199 if(tc < 1.) tc = 1.;
2200 if(tc > 95.) tc = 95.;
2201 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2202 y = ((double)r - minRating)/(maxRating - minRating)
2203 * (h-vMargin-squareSize/8-1) + vMargin;
2204 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2205 if(strstr(seekAdList[i], " u ")) color = 1;
2206 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2207 !strstr(seekAdList[i], "bullet") &&
2208 !strstr(seekAdList[i], "blitz") &&
2209 !strstr(seekAdList[i], "standard") ) color = 2;
2210 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2211 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2215 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2217 char buf[MSG_SIZ], *ext = "";
2218 VariantClass v = StringToVariant(type);
2219 if(strstr(type, "wild")) {
2220 ext = type + 4; // append wild number
2221 if(v == VariantFischeRandom) type = "chess960"; else
2222 if(v == VariantLoadable) type = "setup"; else
2223 type = VariantName(v);
2225 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2226 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2227 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2228 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2229 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2230 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2231 seekNrList[nrOfSeekAds] = nr;
2232 zList[nrOfSeekAds] = 0;
2233 seekAdList[nrOfSeekAds++] = StrSave(buf);
2234 if(plot) PlotSeekAd(nrOfSeekAds-1);
2241 int x = xList[i], y = yList[i], d=squareSize/4, k;
2242 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2243 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2244 // now replot every dot that overlapped
2245 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2246 int xx = xList[k], yy = yList[k];
2247 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2248 DrawSeekDot(xx, yy, colorList[k]);
2253 RemoveSeekAd(int nr)
2256 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2258 if(seekAdList[i]) free(seekAdList[i]);
2259 seekAdList[i] = seekAdList[--nrOfSeekAds];
2260 seekNrList[i] = seekNrList[nrOfSeekAds];
2261 ratingList[i] = ratingList[nrOfSeekAds];
2262 colorList[i] = colorList[nrOfSeekAds];
2263 tcList[i] = tcList[nrOfSeekAds];
2264 xList[i] = xList[nrOfSeekAds];
2265 yList[i] = yList[nrOfSeekAds];
2266 zList[i] = zList[nrOfSeekAds];
2267 seekAdList[nrOfSeekAds] = NULL;
2273 MatchSoughtLine(char *line)
2275 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2276 int nr, base, inc, u=0; char dummy;
2278 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2279 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2281 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2282 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2283 // match: compact and save the line
2284 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2294 if(!seekGraphUp) return FALSE;
2295 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2296 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2298 DrawSeekBackground(0, 0, w, h);
2299 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2300 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2301 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2302 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2304 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2307 snprintf(buf, MSG_SIZ, "%d", i);
2308 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2311 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2312 for(i=1; i<100; i+=(i<10?1:5)) {
2313 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2314 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2315 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2317 snprintf(buf, MSG_SIZ, "%d", i);
2318 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2321 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2325 int SeekGraphClick(ClickType click, int x, int y, int moving)
2327 static int lastDown = 0, displayed = 0, lastSecond;
2328 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2329 if(click == Release || moving) return FALSE;
2331 soughtPending = TRUE;
2332 SendToICS(ics_prefix);
2333 SendToICS("sought\n"); // should this be "sought all"?
2334 } else { // issue challenge based on clicked ad
2335 int dist = 10000; int i, closest = 0, second = 0;
2336 for(i=0; i<nrOfSeekAds; i++) {
2337 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2338 if(d < dist) { dist = d; closest = i; }
2339 second += (d - zList[i] < 120); // count in-range ads
2340 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2344 second = (second > 1);
2345 if(displayed != closest || second != lastSecond) {
2346 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2347 lastSecond = second; displayed = closest;
2349 if(click == Press) {
2350 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2353 } // on press 'hit', only show info
2354 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2355 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2356 SendToICS(ics_prefix);
2358 return TRUE; // let incoming board of started game pop down the graph
2359 } else if(click == Release) { // release 'miss' is ignored
2360 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2361 if(moving == 2) { // right up-click
2362 nrOfSeekAds = 0; // refresh graph
2363 soughtPending = TRUE;
2364 SendToICS(ics_prefix);
2365 SendToICS("sought\n"); // should this be "sought all"?
2368 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2369 // press miss or release hit 'pop down' seek graph
2370 seekGraphUp = FALSE;
2371 DrawPosition(TRUE, NULL);
2377 read_from_ics(isr, closure, data, count, error)
2384 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2385 #define STARTED_NONE 0
2386 #define STARTED_MOVES 1
2387 #define STARTED_BOARD 2
2388 #define STARTED_OBSERVE 3
2389 #define STARTED_HOLDINGS 4
2390 #define STARTED_CHATTER 5
2391 #define STARTED_COMMENT 6
2392 #define STARTED_MOVES_NOHIDE 7
2394 static int started = STARTED_NONE;
2395 static char parse[20000];
2396 static int parse_pos = 0;
2397 static char buf[BUF_SIZE + 1];
2398 static int firstTime = TRUE, intfSet = FALSE;
2399 static ColorClass prevColor = ColorNormal;
2400 static int savingComment = FALSE;
2401 static int cmatch = 0; // continuation sequence match
2408 int backup; /* [DM] For zippy color lines */
2410 char talker[MSG_SIZ]; // [HGM] chat
2413 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2415 if (appData.debugMode) {
2417 fprintf(debugFP, "<ICS: ");
2418 show_bytes(debugFP, data, count);
2419 fprintf(debugFP, "\n");
2423 if (appData.debugMode) { int f = forwardMostMove;
2424 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2425 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2426 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2429 /* If last read ended with a partial line that we couldn't parse,
2430 prepend it to the new read and try again. */
2431 if (leftover_len > 0) {
2432 for (i=0; i<leftover_len; i++)
2433 buf[i] = buf[leftover_start + i];
2436 /* copy new characters into the buffer */
2437 bp = buf + leftover_len;
2438 buf_len=leftover_len;
2439 for (i=0; i<count; i++)
2442 if (data[i] == '\r')
2445 // join lines split by ICS?
2446 if (!appData.noJoin)
2449 Joining just consists of finding matches against the
2450 continuation sequence, and discarding that sequence
2451 if found instead of copying it. So, until a match
2452 fails, there's nothing to do since it might be the
2453 complete sequence, and thus, something we don't want
2456 if (data[i] == cont_seq[cmatch])
2459 if (cmatch == strlen(cont_seq))
2461 cmatch = 0; // complete match. just reset the counter
2464 it's possible for the ICS to not include the space
2465 at the end of the last word, making our [correct]
2466 join operation fuse two separate words. the server
2467 does this when the space occurs at the width setting.
2469 if (!buf_len || buf[buf_len-1] != ' ')
2480 match failed, so we have to copy what matched before
2481 falling through and copying this character. In reality,
2482 this will only ever be just the newline character, but
2483 it doesn't hurt to be precise.
2485 strncpy(bp, cont_seq, cmatch);
2497 buf[buf_len] = NULLCHAR;
2498 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2503 while (i < buf_len) {
2504 /* Deal with part of the TELNET option negotiation
2505 protocol. We refuse to do anything beyond the
2506 defaults, except that we allow the WILL ECHO option,
2507 which ICS uses to turn off password echoing when we are
2508 directly connected to it. We reject this option
2509 if localLineEditing mode is on (always on in xboard)
2510 and we are talking to port 23, which might be a real
2511 telnet server that will try to keep WILL ECHO on permanently.
2513 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2514 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2515 unsigned char option;
2517 switch ((unsigned char) buf[++i]) {
2519 if (appData.debugMode)
2520 fprintf(debugFP, "\n<WILL ");
2521 switch (option = (unsigned char) buf[++i]) {
2523 if (appData.debugMode)
2524 fprintf(debugFP, "ECHO ");
2525 /* Reply only if this is a change, according
2526 to the protocol rules. */
2527 if (remoteEchoOption) break;
2528 if (appData.localLineEditing &&
2529 atoi(appData.icsPort) == TN_PORT) {
2530 TelnetRequest(TN_DONT, TN_ECHO);
2533 TelnetRequest(TN_DO, TN_ECHO);
2534 remoteEchoOption = TRUE;
2538 if (appData.debugMode)
2539 fprintf(debugFP, "%d ", option);
2540 /* Whatever this is, we don't want it. */
2541 TelnetRequest(TN_DONT, option);
2546 if (appData.debugMode)
2547 fprintf(debugFP, "\n<WONT ");
2548 switch (option = (unsigned char) buf[++i]) {
2550 if (appData.debugMode)
2551 fprintf(debugFP, "ECHO ");
2552 /* Reply only if this is a change, according
2553 to the protocol rules. */
2554 if (!remoteEchoOption) break;
2556 TelnetRequest(TN_DONT, TN_ECHO);
2557 remoteEchoOption = FALSE;
2560 if (appData.debugMode)
2561 fprintf(debugFP, "%d ", (unsigned char) option);
2562 /* Whatever this is, it must already be turned
2563 off, because we never agree to turn on
2564 anything non-default, so according to the
2565 protocol rules, we don't reply. */
2570 if (appData.debugMode)
2571 fprintf(debugFP, "\n<DO ");
2572 switch (option = (unsigned char) buf[++i]) {
2574 /* Whatever this is, we refuse to do it. */
2575 if (appData.debugMode)
2576 fprintf(debugFP, "%d ", option);
2577 TelnetRequest(TN_WONT, option);
2582 if (appData.debugMode)
2583 fprintf(debugFP, "\n<DONT ");
2584 switch (option = (unsigned char) buf[++i]) {
2586 if (appData.debugMode)
2587 fprintf(debugFP, "%d ", option);
2588 /* Whatever this is, we are already not doing
2589 it, because we never agree to do anything
2590 non-default, so according to the protocol
2591 rules, we don't reply. */
2596 if (appData.debugMode)
2597 fprintf(debugFP, "\n<IAC ");
2598 /* Doubled IAC; pass it through */
2602 if (appData.debugMode)
2603 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2604 /* Drop all other telnet commands on the floor */
2607 if (oldi > next_out)
2608 SendToPlayer(&buf[next_out], oldi - next_out);
2614 /* OK, this at least will *usually* work */
2615 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2619 if (loggedOn && !intfSet) {
2620 if (ics_type == ICS_ICC) {
2621 snprintf(str, MSG_SIZ,
2622 "/set-quietly interface %s\n/set-quietly style 12\n",
2624 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2625 strcat(str, "/set-2 51 1\n/set seek 1\n");
2626 } else if (ics_type == ICS_CHESSNET) {
2627 snprintf(str, MSG_SIZ, "/style 12\n");
2629 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2630 strcat(str, programVersion);
2631 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2632 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2633 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2635 strcat(str, "$iset nohighlight 1\n");
2637 strcat(str, "$iset lock 1\n$style 12\n");
2640 NotifyFrontendLogin();
2644 if (started == STARTED_COMMENT) {
2645 /* Accumulate characters in comment */
2646 parse[parse_pos++] = buf[i];
2647 if (buf[i] == '\n') {
2648 parse[parse_pos] = NULLCHAR;
2649 if(chattingPartner>=0) {
2651 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2652 OutputChatMessage(chattingPartner, mess);
2653 chattingPartner = -1;
2654 next_out = i+1; // [HGM] suppress printing in ICS window
2656 if(!suppressKibitz) // [HGM] kibitz
2657 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2658 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2659 int nrDigit = 0, nrAlph = 0, j;
2660 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2661 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2662 parse[parse_pos] = NULLCHAR;
2663 // try to be smart: if it does not look like search info, it should go to
2664 // ICS interaction window after all, not to engine-output window.
2665 for(j=0; j<parse_pos; j++) { // count letters and digits
2666 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2667 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2668 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2670 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2671 int depth=0; float score;
2672 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2673 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2674 pvInfoList[forwardMostMove-1].depth = depth;
2675 pvInfoList[forwardMostMove-1].score = 100*score;
2677 OutputKibitz(suppressKibitz, parse);
2680 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2681 SendToPlayer(tmp, strlen(tmp));
2683 next_out = i+1; // [HGM] suppress printing in ICS window
2685 started = STARTED_NONE;
2687 /* Don't match patterns against characters in comment */
2692 if (started == STARTED_CHATTER) {
2693 if (buf[i] != '\n') {
2694 /* Don't match patterns against characters in chatter */
2698 started = STARTED_NONE;
2699 if(suppressKibitz) next_out = i+1;
2702 /* Kludge to deal with rcmd protocol */
2703 if (firstTime && looking_at(buf, &i, "\001*")) {
2704 DisplayFatalError(&buf[1], 0, 1);
2710 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2713 if (appData.debugMode)
2714 fprintf(debugFP, "ics_type %d\n", ics_type);
2717 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2718 ics_type = ICS_FICS;
2720 if (appData.debugMode)
2721 fprintf(debugFP, "ics_type %d\n", ics_type);
2724 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2725 ics_type = ICS_CHESSNET;
2727 if (appData.debugMode)
2728 fprintf(debugFP, "ics_type %d\n", ics_type);
2733 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2734 looking_at(buf, &i, "Logging you in as \"*\"") ||
2735 looking_at(buf, &i, "will be \"*\""))) {
2736 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2740 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2742 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2743 DisplayIcsInteractionTitle(buf);
2744 have_set_title = TRUE;
2747 /* skip finger notes */
2748 if (started == STARTED_NONE &&
2749 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2750 (buf[i] == '1' && buf[i+1] == '0')) &&
2751 buf[i+2] == ':' && buf[i+3] == ' ') {
2752 started = STARTED_CHATTER;
2758 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2759 if(appData.seekGraph) {
2760 if(soughtPending && MatchSoughtLine(buf+i)) {
2761 i = strstr(buf+i, "rated") - buf;
2762 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2763 next_out = leftover_start = i;
2764 started = STARTED_CHATTER;
2765 suppressKibitz = TRUE;
2768 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2769 && looking_at(buf, &i, "* ads displayed")) {
2770 soughtPending = FALSE;
2775 if(appData.autoRefresh) {
2776 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2777 int s = (ics_type == ICS_ICC); // ICC format differs
2779 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2780 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2781 looking_at(buf, &i, "*% "); // eat prompt
2782 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2783 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2784 next_out = i; // suppress
2787 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2788 char *p = star_match[0];
2790 if(seekGraphUp) RemoveSeekAd(atoi(p));
2791 while(*p && *p++ != ' '); // next
2793 looking_at(buf, &i, "*% "); // eat prompt
2794 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2801 /* skip formula vars */
2802 if (started == STARTED_NONE &&
2803 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2804 started = STARTED_CHATTER;
2809 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2810 if (appData.autoKibitz && started == STARTED_NONE &&
2811 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2812 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2813 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2814 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2815 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2816 suppressKibitz = TRUE;
2817 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2819 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2820 && (gameMode == IcsPlayingWhite)) ||
2821 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2822 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2823 started = STARTED_CHATTER; // own kibitz we simply discard
2825 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2826 parse_pos = 0; parse[0] = NULLCHAR;
2827 savingComment = TRUE;
2828 suppressKibitz = gameMode != IcsObserving ? 2 :
2829 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2833 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2834 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2835 && atoi(star_match[0])) {
2836 // suppress the acknowledgements of our own autoKibitz
2838 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2839 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2840 SendToPlayer(star_match[0], strlen(star_match[0]));
2841 if(looking_at(buf, &i, "*% ")) // eat prompt
2842 suppressKibitz = FALSE;
2846 } // [HGM] kibitz: end of patch
2848 // [HGM] chat: intercept tells by users for which we have an open chat window
2850 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2851 looking_at(buf, &i, "* whispers:") ||
2852 looking_at(buf, &i, "* kibitzes:") ||
2853 looking_at(buf, &i, "* shouts:") ||
2854 looking_at(buf, &i, "* c-shouts:") ||
2855 looking_at(buf, &i, "--> * ") ||
2856 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2857 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2858 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2859 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2861 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2862 chattingPartner = -1;
2864 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2865 for(p=0; p<MAX_CHAT; p++) {
2866 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2867 talker[0] = '['; strcat(talker, "] ");
2868 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2869 chattingPartner = p; break;
2872 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2873 for(p=0; p<MAX_CHAT; p++) {
2874 if(!strcmp("kibitzes", chatPartner[p])) {
2875 talker[0] = '['; strcat(talker, "] ");
2876 chattingPartner = p; break;
2879 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2880 for(p=0; p<MAX_CHAT; p++) {
2881 if(!strcmp("whispers", chatPartner[p])) {
2882 talker[0] = '['; strcat(talker, "] ");
2883 chattingPartner = p; break;
2886 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2887 if(buf[i-8] == '-' && buf[i-3] == 't')
2888 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2889 if(!strcmp("c-shouts", chatPartner[p])) {
2890 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2891 chattingPartner = p; break;
2894 if(chattingPartner < 0)
2895 for(p=0; p<MAX_CHAT; p++) {
2896 if(!strcmp("shouts", chatPartner[p])) {
2897 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2898 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2899 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2900 chattingPartner = p; break;
2904 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2905 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2906 talker[0] = 0; Colorize(ColorTell, FALSE);
2907 chattingPartner = p; break;
2909 if(chattingPartner<0) i = oldi; else {
2910 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2911 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2912 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2913 started = STARTED_COMMENT;
2914 parse_pos = 0; parse[0] = NULLCHAR;
2915 savingComment = 3 + chattingPartner; // counts as TRUE
2916 suppressKibitz = TRUE;
2919 } // [HGM] chat: end of patch
2921 if (appData.zippyTalk || appData.zippyPlay) {
2922 /* [DM] Backup address for color zippy lines */
2925 if (loggedOn == TRUE)
2926 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2927 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2929 } // [DM] 'else { ' deleted
2931 /* Regular tells and says */
2932 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2933 looking_at(buf, &i, "* (your partner) tells you: ") ||
2934 looking_at(buf, &i, "* says: ") ||
2935 /* Don't color "message" or "messages" output */
2936 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2937 looking_at(buf, &i, "*. * at *:*: ") ||
2938 looking_at(buf, &i, "--* (*:*): ") ||
2939 /* Message notifications (same color as tells) */
2940 looking_at(buf, &i, "* has left a message ") ||
2941 looking_at(buf, &i, "* just sent you a message:\n") ||
2942 /* Whispers and kibitzes */
2943 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2944 looking_at(buf, &i, "* kibitzes: ") ||
2946 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2948 if (tkind == 1 && strchr(star_match[0], ':')) {
2949 /* Avoid "tells you:" spoofs in channels */
2952 if (star_match[0][0] == NULLCHAR ||
2953 strchr(star_match[0], ' ') ||
2954 (tkind == 3 && strchr(star_match[1], ' '))) {
2955 /* Reject bogus matches */
2958 if (appData.colorize) {
2959 if (oldi > next_out) {
2960 SendToPlayer(&buf[next_out], oldi - next_out);
2965 Colorize(ColorTell, FALSE);
2966 curColor = ColorTell;
2969 Colorize(ColorKibitz, FALSE);
2970 curColor = ColorKibitz;
2973 p = strrchr(star_match[1], '(');
2980 Colorize(ColorChannel1, FALSE);
2981 curColor = ColorChannel1;
2983 Colorize(ColorChannel, FALSE);
2984 curColor = ColorChannel;
2988 curColor = ColorNormal;
2992 if (started == STARTED_NONE && appData.autoComment &&
2993 (gameMode == IcsObserving ||
2994 gameMode == IcsPlayingWhite ||
2995 gameMode == IcsPlayingBlack)) {
2996 parse_pos = i - oldi;
2997 memcpy(parse, &buf[oldi], parse_pos);
2998 parse[parse_pos] = NULLCHAR;
2999 started = STARTED_COMMENT;
3000 savingComment = TRUE;
3002 started = STARTED_CHATTER;
3003 savingComment = FALSE;
3010 if (looking_at(buf, &i, "* s-shouts: ") ||
3011 looking_at(buf, &i, "* c-shouts: ")) {
3012 if (appData.colorize) {
3013 if (oldi > next_out) {
3014 SendToPlayer(&buf[next_out], oldi - next_out);
3017 Colorize(ColorSShout, FALSE);
3018 curColor = ColorSShout;
3021 started = STARTED_CHATTER;
3025 if (looking_at(buf, &i, "--->")) {
3030 if (looking_at(buf, &i, "* shouts: ") ||
3031 looking_at(buf, &i, "--> ")) {
3032 if (appData.colorize) {
3033 if (oldi > next_out) {
3034 SendToPlayer(&buf[next_out], oldi - next_out);
3037 Colorize(ColorShout, FALSE);
3038 curColor = ColorShout;
3041 started = STARTED_CHATTER;
3045 if (looking_at( buf, &i, "Challenge:")) {
3046 if (appData.colorize) {
3047 if (oldi > next_out) {
3048 SendToPlayer(&buf[next_out], oldi - next_out);
3051 Colorize(ColorChallenge, FALSE);
3052 curColor = ColorChallenge;
3058 if (looking_at(buf, &i, "* offers you") ||
3059 looking_at(buf, &i, "* offers to be") ||
3060 looking_at(buf, &i, "* would like to") ||
3061 looking_at(buf, &i, "* requests to") ||
3062 looking_at(buf, &i, "Your opponent offers") ||
3063 looking_at(buf, &i, "Your opponent requests")) {
3065 if (appData.colorize) {
3066 if (oldi > next_out) {
3067 SendToPlayer(&buf[next_out], oldi - next_out);
3070 Colorize(ColorRequest, FALSE);
3071 curColor = ColorRequest;
3076 if (looking_at(buf, &i, "* (*) seeking")) {
3077 if (appData.colorize) {
3078 if (oldi > next_out) {
3079 SendToPlayer(&buf[next_out], oldi - next_out);
3082 Colorize(ColorSeek, FALSE);
3083 curColor = ColorSeek;
3088 if (looking_at(buf, &i, "\\ ")) {
3089 if (prevColor != ColorNormal) {
3090 if (oldi > next_out) {
3091 SendToPlayer(&buf[next_out], oldi - next_out);
3094 Colorize(prevColor, TRUE);
3095 curColor = prevColor;
3097 if (savingComment) {
3098 parse_pos = i - oldi;
3099 memcpy(parse, &buf[oldi], parse_pos);
3100 parse[parse_pos] = NULLCHAR;
3101 started = STARTED_COMMENT;
3102 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3103 chattingPartner = savingComment - 3; // kludge to remember the box
3105 started = STARTED_CHATTER;
3110 if (looking_at(buf, &i, "Black Strength :") ||
3111 looking_at(buf, &i, "<<< style 10 board >>>") ||
3112 looking_at(buf, &i, "<10>") ||
3113 looking_at(buf, &i, "#@#")) {
3114 /* Wrong board style */
3116 SendToICS(ics_prefix);
3117 SendToICS("set style 12\n");
3118 SendToICS(ics_prefix);
3119 SendToICS("refresh\n");
3123 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3125 have_sent_ICS_logon = 1;
3129 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3130 (looking_at(buf, &i, "\n<12> ") ||
3131 looking_at(buf, &i, "<12> "))) {
3133 if (oldi > next_out) {
3134 SendToPlayer(&buf[next_out], oldi - next_out);
3137 started = STARTED_BOARD;
3142 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3143 looking_at(buf, &i, "<b1> ")) {
3144 if (oldi > next_out) {
3145 SendToPlayer(&buf[next_out], oldi - next_out);
3148 started = STARTED_HOLDINGS;
3153 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3155 /* Header for a move list -- first line */
3157 switch (ics_getting_history) {
3161 case BeginningOfGame:
3162 /* User typed "moves" or "oldmoves" while we
3163 were idle. Pretend we asked for these
3164 moves and soak them up so user can step
3165 through them and/or save them.
3168 gameMode = IcsObserving;
3171 ics_getting_history = H_GOT_UNREQ_HEADER;
3173 case EditGame: /*?*/
3174 case EditPosition: /*?*/
3175 /* Should above feature work in these modes too? */
3176 /* For now it doesn't */
3177 ics_getting_history = H_GOT_UNWANTED_HEADER;
3180 ics_getting_history = H_GOT_UNWANTED_HEADER;
3185 /* Is this the right one? */
3186 if (gameInfo.white && gameInfo.black &&
3187 strcmp(gameInfo.white, star_match[0]) == 0 &&
3188 strcmp(gameInfo.black, star_match[2]) == 0) {
3190 ics_getting_history = H_GOT_REQ_HEADER;
3193 case H_GOT_REQ_HEADER:
3194 case H_GOT_UNREQ_HEADER:
3195 case H_GOT_UNWANTED_HEADER:
3196 case H_GETTING_MOVES:
3197 /* Should not happen */
3198 DisplayError(_("Error gathering move list: two headers"), 0);
3199 ics_getting_history = H_FALSE;
3203 /* Save player ratings into gameInfo if needed */
3204 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3205 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3206 (gameInfo.whiteRating == -1 ||
3207 gameInfo.blackRating == -1)) {
3209 gameInfo.whiteRating = string_to_rating(star_match[1]);
3210 gameInfo.blackRating = string_to_rating(star_match[3]);
3211 if (appData.debugMode)
3212 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3213 gameInfo.whiteRating, gameInfo.blackRating);
3218 if (looking_at(buf, &i,
3219 "* * match, initial time: * minute*, increment: * second")) {
3220 /* Header for a move list -- second line */
3221 /* Initial board will follow if this is a wild game */
3222 if (gameInfo.event != NULL) free(gameInfo.event);
3223 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3224 gameInfo.event = StrSave(str);
3225 /* [HGM] we switched variant. Translate boards if needed. */
3226 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3230 if (looking_at(buf, &i, "Move ")) {
3231 /* Beginning of a move list */
3232 switch (ics_getting_history) {
3234 /* Normally should not happen */
3235 /* Maybe user hit reset while we were parsing */
3238 /* Happens if we are ignoring a move list that is not
3239 * the one we just requested. Common if the user
3240 * tries to observe two games without turning off
3243 case H_GETTING_MOVES:
3244 /* Should not happen */
3245 DisplayError(_("Error gathering move list: nested"), 0);
3246 ics_getting_history = H_FALSE;
3248 case H_GOT_REQ_HEADER:
3249 ics_getting_history = H_GETTING_MOVES;
3250 started = STARTED_MOVES;
3252 if (oldi > next_out) {
3253 SendToPlayer(&buf[next_out], oldi - next_out);
3256 case H_GOT_UNREQ_HEADER:
3257 ics_getting_history = H_GETTING_MOVES;
3258 started = STARTED_MOVES_NOHIDE;
3261 case H_GOT_UNWANTED_HEADER:
3262 ics_getting_history = H_FALSE;
3268 if (looking_at(buf, &i, "% ") ||
3269 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3270 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3271 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3272 soughtPending = FALSE;
3276 if(suppressKibitz) next_out = i;
3277 savingComment = FALSE;
3281 case STARTED_MOVES_NOHIDE:
3282 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3283 parse[parse_pos + i - oldi] = NULLCHAR;
3284 ParseGameHistory(parse);
3286 if (appData.zippyPlay && first.initDone) {
3287 FeedMovesToProgram(&first, forwardMostMove);
3288 if (gameMode == IcsPlayingWhite) {
3289 if (WhiteOnMove(forwardMostMove)) {
3290 if (first.sendTime) {
3291 if (first.useColors) {
3292 SendToProgram("black\n", &first);
3294 SendTimeRemaining(&first, TRUE);
3296 if (first.useColors) {
3297 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3299 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3300 first.maybeThinking = TRUE;
3302 if (first.usePlayother) {
3303 if (first.sendTime) {
3304 SendTimeRemaining(&first, TRUE);
3306 SendToProgram("playother\n", &first);
3312 } else if (gameMode == IcsPlayingBlack) {
3313 if (!WhiteOnMove(forwardMostMove)) {
3314 if (first.sendTime) {
3315 if (first.useColors) {
3316 SendToProgram("white\n", &first);
3318 SendTimeRemaining(&first, FALSE);
3320 if (first.useColors) {
3321 SendToProgram("black\n", &first);
3323 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3324 first.maybeThinking = TRUE;
3326 if (first.usePlayother) {
3327 if (first.sendTime) {
3328 SendTimeRemaining(&first, FALSE);
3330 SendToProgram("playother\n", &first);
3339 if (gameMode == IcsObserving && ics_gamenum == -1) {
3340 /* Moves came from oldmoves or moves command
3341 while we weren't doing anything else.
3343 currentMove = forwardMostMove;
3344 ClearHighlights();/*!!could figure this out*/
3345 flipView = appData.flipView;
3346 DrawPosition(TRUE, boards[currentMove]);
3347 DisplayBothClocks();
3348 snprintf(str, MSG_SIZ, "%s vs. %s",
3349 gameInfo.white, gameInfo.black);
3353 /* Moves were history of an active game */
3354 if (gameInfo.resultDetails != NULL) {
3355 free(gameInfo.resultDetails);
3356 gameInfo.resultDetails = NULL;
3359 HistorySet(parseList, backwardMostMove,
3360 forwardMostMove, currentMove-1);
3361 DisplayMove(currentMove - 1);
3362 if (started == STARTED_MOVES) next_out = i;
3363 started = STARTED_NONE;
3364 ics_getting_history = H_FALSE;
3367 case STARTED_OBSERVE:
3368 started = STARTED_NONE;
3369 SendToICS(ics_prefix);
3370 SendToICS("refresh\n");
3376 if(bookHit) { // [HGM] book: simulate book reply
3377 static char bookMove[MSG_SIZ]; // a bit generous?
3379 programStats.nodes = programStats.depth = programStats.time =
3380 programStats.score = programStats.got_only_move = 0;
3381 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3383 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3384 strcat(bookMove, bookHit);
3385 HandleMachineMove(bookMove, &first);
3390 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3391 started == STARTED_HOLDINGS ||
3392 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3393 /* Accumulate characters in move list or board */
3394 parse[parse_pos++] = buf[i];
3397 /* Start of game messages. Mostly we detect start of game
3398 when the first board image arrives. On some versions
3399 of the ICS, though, we need to do a "refresh" after starting
3400 to observe in order to get the current board right away. */
3401 if (looking_at(buf, &i, "Adding game * to observation list")) {
3402 started = STARTED_OBSERVE;
3406 /* Handle auto-observe */
3407 if (appData.autoObserve &&
3408 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3409 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3411 /* Choose the player that was highlighted, if any. */
3412 if (star_match[0][0] == '\033' ||
3413 star_match[1][0] != '\033') {
3414 player = star_match[0];
3416 player = star_match[2];
3418 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3419 ics_prefix, StripHighlightAndTitle(player));
3422 /* Save ratings from notify string */
3423 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3424 player1Rating = string_to_rating(star_match[1]);
3425 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3426 player2Rating = string_to_rating(star_match[3]);
3428 if (appData.debugMode)
3430 "Ratings from 'Game notification:' %s %d, %s %d\n",
3431 player1Name, player1Rating,
3432 player2Name, player2Rating);
3437 /* Deal with automatic examine mode after a game,
3438 and with IcsObserving -> IcsExamining transition */
3439 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3440 looking_at(buf, &i, "has made you an examiner of game *")) {
3442 int gamenum = atoi(star_match[0]);
3443 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3444 gamenum == ics_gamenum) {
3445 /* We were already playing or observing this game;
3446 no need to refetch history */
3447 gameMode = IcsExamining;
3449 pauseExamForwardMostMove = forwardMostMove;
3450 } else if (currentMove < forwardMostMove) {
3451 ForwardInner(forwardMostMove);
3454 /* I don't think this case really can happen */
3455 SendToICS(ics_prefix);
3456 SendToICS("refresh\n");
3461 /* Error messages */
3462 // if (ics_user_moved) {
3463 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3464 if (looking_at(buf, &i, "Illegal move") ||
3465 looking_at(buf, &i, "Not a legal move") ||
3466 looking_at(buf, &i, "Your king is in check") ||
3467 looking_at(buf, &i, "It isn't your turn") ||
3468 looking_at(buf, &i, "It is not your move")) {
3470 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3471 currentMove = forwardMostMove-1;
3472 DisplayMove(currentMove - 1); /* before DMError */
3473 DrawPosition(FALSE, boards[currentMove]);
3474 SwitchClocks(forwardMostMove-1); // [HGM] race
3475 DisplayBothClocks();
3477 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3483 if (looking_at(buf, &i, "still have time") ||
3484 looking_at(buf, &i, "not out of time") ||
3485 looking_at(buf, &i, "either player is out of time") ||
3486 looking_at(buf, &i, "has timeseal; checking")) {
3487 /* We must have called his flag a little too soon */
3488 whiteFlag = blackFlag = FALSE;
3492 if (looking_at(buf, &i, "added * seconds to") ||
3493 looking_at(buf, &i, "seconds were added to")) {
3494 /* Update the clocks */
3495 SendToICS(ics_prefix);
3496 SendToICS("refresh\n");
3500 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3501 ics_clock_paused = TRUE;
3506 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3507 ics_clock_paused = FALSE;
3512 /* Grab player ratings from the Creating: message.
3513 Note we have to check for the special case when
3514 the ICS inserts things like [white] or [black]. */
3515 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3516 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3518 0 player 1 name (not necessarily white)
3520 2 empty, white, or black (IGNORED)
3521 3 player 2 name (not necessarily black)
3524 The names/ratings are sorted out when the game
3525 actually starts (below).
3527 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3528 player1Rating = string_to_rating(star_match[1]);
3529 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3530 player2Rating = string_to_rating(star_match[4]);
3532 if (appData.debugMode)
3534 "Ratings from 'Creating:' %s %d, %s %d\n",
3535 player1Name, player1Rating,
3536 player2Name, player2Rating);
3541 /* Improved generic start/end-of-game messages */
3542 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3543 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3544 /* If tkind == 0: */
3545 /* star_match[0] is the game number */
3546 /* [1] is the white player's name */
3547 /* [2] is the black player's name */
3548 /* For end-of-game: */
3549 /* [3] is the reason for the game end */
3550 /* [4] is a PGN end game-token, preceded by " " */
3551 /* For start-of-game: */
3552 /* [3] begins with "Creating" or "Continuing" */
3553 /* [4] is " *" or empty (don't care). */
3554 int gamenum = atoi(star_match[0]);
3555 char *whitename, *blackname, *why, *endtoken;
3556 ChessMove endtype = EndOfFile;
3559 whitename = star_match[1];
3560 blackname = star_match[2];
3561 why = star_match[3];
3562 endtoken = star_match[4];
3564 whitename = star_match[1];
3565 blackname = star_match[3];
3566 why = star_match[5];
3567 endtoken = star_match[6];
3570 /* Game start messages */
3571 if (strncmp(why, "Creating ", 9) == 0 ||
3572 strncmp(why, "Continuing ", 11) == 0) {
3573 gs_gamenum = gamenum;
3574 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3575 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3577 if (appData.zippyPlay) {
3578 ZippyGameStart(whitename, blackname);
3581 partnerBoardValid = FALSE; // [HGM] bughouse
3585 /* Game end messages */
3586 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3587 ics_gamenum != gamenum) {
3590 while (endtoken[0] == ' ') endtoken++;
3591 switch (endtoken[0]) {
3594 endtype = GameUnfinished;
3597 endtype = BlackWins;
3600 if (endtoken[1] == '/')
3601 endtype = GameIsDrawn;
3603 endtype = WhiteWins;
3606 GameEnds(endtype, why, GE_ICS);
3608 if (appData.zippyPlay && first.initDone) {
3609 ZippyGameEnd(endtype, why);
3610 if (first.pr == NULL) {
3611 /* Start the next process early so that we'll
3612 be ready for the next challenge */
3613 StartChessProgram(&first);
3615 /* Send "new" early, in case this command takes
3616 a long time to finish, so that we'll be ready
3617 for the next challenge. */
3618 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3622 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3626 if (looking_at(buf, &i, "Removing game * from observation") ||
3627 looking_at(buf, &i, "no longer observing game *") ||
3628 looking_at(buf, &i, "Game * (*) has no examiners")) {
3629 if (gameMode == IcsObserving &&
3630 atoi(star_match[0]) == ics_gamenum)
3632 /* icsEngineAnalyze */
3633 if (appData.icsEngineAnalyze) {
3640 ics_user_moved = FALSE;
3645 if (looking_at(buf, &i, "no longer examining game *")) {
3646 if (gameMode == IcsExamining &&
3647 atoi(star_match[0]) == ics_gamenum)
3651 ics_user_moved = FALSE;
3656 /* Advance leftover_start past any newlines we find,
3657 so only partial lines can get reparsed */
3658 if (looking_at(buf, &i, "\n")) {
3659 prevColor = curColor;
3660 if (curColor != ColorNormal) {
3661 if (oldi > next_out) {
3662 SendToPlayer(&buf[next_out], oldi - next_out);
3665 Colorize(ColorNormal, FALSE);
3666 curColor = ColorNormal;
3668 if (started == STARTED_BOARD) {
3669 started = STARTED_NONE;
3670 parse[parse_pos] = NULLCHAR;
3671 ParseBoard12(parse);
3674 /* Send premove here */
3675 if (appData.premove) {
3677 if (currentMove == 0 &&
3678 gameMode == IcsPlayingWhite &&
3679 appData.premoveWhite) {
3680 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3681 if (appData.debugMode)
3682 fprintf(debugFP, "Sending premove:\n");
3684 } else if (currentMove == 1 &&
3685 gameMode == IcsPlayingBlack &&
3686 appData.premoveBlack) {
3687 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3688 if (appData.debugMode)
3689 fprintf(debugFP, "Sending premove:\n");
3691 } else if (gotPremove) {
3693 ClearPremoveHighlights();
3694 if (appData.debugMode)
3695 fprintf(debugFP, "Sending premove:\n");
3696 UserMoveEvent(premoveFromX, premoveFromY,
3697 premoveToX, premoveToY,
3702 /* Usually suppress following prompt */
3703 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3704 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3705 if (looking_at(buf, &i, "*% ")) {
3706 savingComment = FALSE;
3711 } else if (started == STARTED_HOLDINGS) {
3713 char new_piece[MSG_SIZ];
3714 started = STARTED_NONE;
3715 parse[parse_pos] = NULLCHAR;
3716 if (appData.debugMode)
3717 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3718 parse, currentMove);
3719 if (sscanf(parse, " game %d", &gamenum) == 1) {
3720 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3721 if (gameInfo.variant == VariantNormal) {
3722 /* [HGM] We seem to switch variant during a game!
3723 * Presumably no holdings were displayed, so we have
3724 * to move the position two files to the right to
3725 * create room for them!
3727 VariantClass newVariant;
3728 switch(gameInfo.boardWidth) { // base guess on board width
3729 case 9: newVariant = VariantShogi; break;
3730 case 10: newVariant = VariantGreat; break;
3731 default: newVariant = VariantCrazyhouse; break;
3733 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3734 /* Get a move list just to see the header, which
3735 will tell us whether this is really bug or zh */
3736 if (ics_getting_history == H_FALSE) {
3737 ics_getting_history = H_REQUESTED;
3738 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3742 new_piece[0] = NULLCHAR;
3743 sscanf(parse, "game %d white [%s black [%s <- %s",
3744 &gamenum, white_holding, black_holding,
3746 white_holding[strlen(white_holding)-1] = NULLCHAR;
3747 black_holding[strlen(black_holding)-1] = NULLCHAR;
3748 /* [HGM] copy holdings to board holdings area */
3749 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3750 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3751 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3753 if (appData.zippyPlay && first.initDone) {
3754 ZippyHoldings(white_holding, black_holding,
3758 if (tinyLayout || smallLayout) {
3759 char wh[16], bh[16];
3760 PackHolding(wh, white_holding);
3761 PackHolding(bh, black_holding);
3762 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3763 gameInfo.white, gameInfo.black);
3765 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3766 gameInfo.white, white_holding,
3767 gameInfo.black, black_holding);
3769 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3770 DrawPosition(FALSE, boards[currentMove]);
3772 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3773 sscanf(parse, "game %d white [%s black [%s <- %s",
3774 &gamenum, white_holding, black_holding,
3776 white_holding[strlen(white_holding)-1] = NULLCHAR;
3777 black_holding[strlen(black_holding)-1] = NULLCHAR;
3778 /* [HGM] copy holdings to partner-board holdings area */
3779 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3780 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3781 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3782 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3783 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3786 /* Suppress following prompt */
3787 if (looking_at(buf, &i, "*% ")) {
3788 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3789 savingComment = FALSE;
3797 i++; /* skip unparsed character and loop back */
3800 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3801 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3802 // SendToPlayer(&buf[next_out], i - next_out);
3803 started != STARTED_HOLDINGS && leftover_start > next_out) {
3804 SendToPlayer(&buf[next_out], leftover_start - next_out);
3808 leftover_len = buf_len - leftover_start;
3809 /* if buffer ends with something we couldn't parse,
3810 reparse it after appending the next read */