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;
272 /* States for ics_getting_history */
274 #define H_REQUESTED 1
275 #define H_GOT_REQ_HEADER 2
276 #define H_GOT_UNREQ_HEADER 3
277 #define H_GETTING_MOVES 4
278 #define H_GOT_UNWANTED_HEADER 5
280 /* whosays values for GameEnds */
289 /* Maximum number of games in a cmail message */
290 #define CMAIL_MAX_GAMES 20
292 /* Different types of move when calling RegisterMove */
294 #define CMAIL_RESIGN 1
296 #define CMAIL_ACCEPT 3
298 /* Different types of result to remember for each game */
299 #define CMAIL_NOT_RESULT 0
300 #define CMAIL_OLD_RESULT 1
301 #define CMAIL_NEW_RESULT 2
303 /* Telnet protocol constants */
314 safeStrCpy( char *dst, const char *src, size_t count )
317 assert( dst != NULL );
318 assert( src != NULL );
321 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
322 if( i == count && dst[count-1] != NULLCHAR)
324 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
325 if(appData.debugMode)
326 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
332 /* Some compiler can't cast u64 to double
333 * This function do the job for us:
335 * We use the highest bit for cast, this only
336 * works if the highest bit is not
337 * in use (This should not happen)
339 * We used this for all compiler
342 u64ToDouble(u64 value)
345 u64 tmp = value & u64Const(0x7fffffffffffffff);
346 r = (double)(s64)tmp;
347 if (value & u64Const(0x8000000000000000))
348 r += 9.2233720368547758080e18; /* 2^63 */
352 /* Fake up flags for now, as we aren't keeping track of castling
353 availability yet. [HGM] Change of logic: the flag now only
354 indicates the type of castlings allowed by the rule of the game.
355 The actual rights themselves are maintained in the array
356 castlingRights, as part of the game history, and are not probed
362 int flags = F_ALL_CASTLE_OK;
363 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
364 switch (gameInfo.variant) {
366 flags &= ~F_ALL_CASTLE_OK;
367 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
368 flags |= F_IGNORE_CHECK;
370 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375 case VariantKriegspiel:
376 flags |= F_KRIEGSPIEL_CAPTURE;
378 case VariantCapaRandom:
379 case VariantFischeRandom:
380 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
381 case VariantNoCastle:
382 case VariantShatranj:
385 flags &= ~F_ALL_CASTLE_OK;
393 FILE *gameFileFP, *debugFP;
396 [AS] Note: sometimes, the sscanf() function is used to parse the input
397 into a fixed-size buffer. Because of this, we must be prepared to
398 receive strings as long as the size of the input buffer, which is currently
399 set to 4K for Windows and 8K for the rest.
400 So, we must either allocate sufficiently large buffers here, or
401 reduce the size of the input buffer in the input reading part.
404 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
405 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
406 char thinkOutput1[MSG_SIZ*10];
408 ChessProgramState first, second;
410 /* premove variables */
413 int premoveFromX = 0;
414 int premoveFromY = 0;
415 int premovePromoChar = 0;
417 Boolean alarmSounded;
418 /* end premove variables */
420 char *ics_prefix = "$";
421 int ics_type = ICS_GENERIC;
423 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
424 int pauseExamForwardMostMove = 0;
425 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
426 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
427 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
428 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
429 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
430 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
431 int whiteFlag = FALSE, blackFlag = FALSE;
432 int userOfferedDraw = FALSE;
433 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
434 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
435 int cmailMoveType[CMAIL_MAX_GAMES];
436 long ics_clock_paused = 0;
437 ProcRef icsPR = NoProc, cmailPR = NoProc;
438 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
439 GameMode gameMode = BeginningOfGame;
440 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
441 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
442 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
443 int hiddenThinkOutputState = 0; /* [AS] */
444 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
445 int adjudicateLossPlies = 6;
446 char white_holding[64], black_holding[64];
447 TimeMark lastNodeCountTime;
448 long lastNodeCount=0;
449 int shiftKey; // [HGM] set by mouse handler
451 int have_sent_ICS_logon = 0;
453 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
454 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
455 long timeControl_2; /* [AS] Allow separate time controls */
456 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
457 long timeRemaining[2][MAX_MOVES];
459 TimeMark programStartTime;
460 char ics_handle[MSG_SIZ];
461 int have_set_title = 0;
463 /* animateTraining preserves the state of appData.animate
464 * when Training mode is activated. This allows the
465 * response to be animated when appData.animate == TRUE and
466 * appData.animateDragging == TRUE.
468 Boolean animateTraining;
474 Board boards[MAX_MOVES];
475 /* [HGM] Following 7 needed for accurate legality tests: */
476 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
477 signed char initialRights[BOARD_FILES];
478 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
479 int initialRulePlies, FENrulePlies;
480 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int mute; // mute all sounds
485 // [HGM] vari: next 12 to save and restore variations
486 #define MAX_VARIATIONS 10
487 int framePtr = MAX_MOVES-1; // points to free stack entry
489 int savedFirst[MAX_VARIATIONS];
490 int savedLast[MAX_VARIATIONS];
491 int savedFramePtr[MAX_VARIATIONS];
492 char *savedDetails[MAX_VARIATIONS];
493 ChessMove savedResult[MAX_VARIATIONS];
495 void PushTail P((int firstMove, int lastMove));
496 Boolean PopTail P((Boolean annotate));
497 void CleanupTail P((void));
499 ChessSquare FIDEArray[2][BOARD_FILES] = {
500 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
501 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
502 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
503 BlackKing, BlackBishop, BlackKnight, BlackRook }
506 ChessSquare twoKingsArray[2][BOARD_FILES] = {
507 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
509 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510 BlackKing, BlackKing, BlackKnight, BlackRook }
513 ChessSquare KnightmateArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
515 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
516 { BlackRook, BlackMan, BlackBishop, BlackQueen,
517 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 ChessSquare SpartanArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
523 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
524 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
527 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
528 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
531 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
534 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
535 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
536 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
537 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
538 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
541 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
543 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackMan, BlackFerz,
545 BlackKing, BlackMan, BlackKnight, BlackRook }
549 #if (BOARD_FILES>=10)
550 ChessSquare ShogiArray[2][BOARD_FILES] = {
551 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
552 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
553 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
554 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
557 ChessSquare XiangqiArray[2][BOARD_FILES] = {
558 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
559 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
560 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
561 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
564 ChessSquare CapablancaArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
566 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
567 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
568 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
571 ChessSquare GreatArray[2][BOARD_FILES] = {
572 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
573 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
574 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
575 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
578 ChessSquare JanusArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
580 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
581 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
582 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
586 ChessSquare GothicArray[2][BOARD_FILES] = {
587 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
588 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
590 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
593 #define GothicArray CapablancaArray
597 ChessSquare FalconArray[2][BOARD_FILES] = {
598 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
599 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
600 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
601 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
604 #define FalconArray CapablancaArray
607 #else // !(BOARD_FILES>=10)
608 #define XiangqiPosition FIDEArray
609 #define CapablancaArray FIDEArray
610 #define GothicArray FIDEArray
611 #define GreatArray FIDEArray
612 #endif // !(BOARD_FILES>=10)
614 #if (BOARD_FILES>=12)
615 ChessSquare CourierArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
617 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
619 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
621 #else // !(BOARD_FILES>=12)
622 #define CourierArray CapablancaArray
623 #endif // !(BOARD_FILES>=12)
626 Board initialPosition;
629 /* Convert str to a rating. Checks for special cases of "----",
631 "++++", etc. Also strips ()'s */
633 string_to_rating(str)
636 while(*str && !isdigit(*str)) ++str;
638 return 0; /* One of the special "no rating" cases */
646 /* Init programStats */
647 programStats.movelist[0] = 0;
648 programStats.depth = 0;
649 programStats.nr_moves = 0;
650 programStats.moves_left = 0;
651 programStats.nodes = 0;
652 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
653 programStats.score = 0;
654 programStats.got_only_move = 0;
655 programStats.got_fail = 0;
656 programStats.line_is_book = 0;
662 int matched, min, sec;
664 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
665 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
667 GetTimeMark(&programStartTime);
668 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
671 programStats.ok_to_send = 1;
672 programStats.seen_stat = 0;
675 * Initialize game list
681 * Internet chess server status
683 if (appData.icsActive) {
684 appData.matchMode = FALSE;
685 appData.matchGames = 0;
687 appData.noChessProgram = !appData.zippyPlay;
689 appData.zippyPlay = FALSE;
690 appData.zippyTalk = FALSE;
691 appData.noChessProgram = TRUE;
693 if (*appData.icsHelper != NULLCHAR) {
694 appData.useTelnet = TRUE;
695 appData.telnetProgram = appData.icsHelper;
698 appData.zippyTalk = appData.zippyPlay = FALSE;
701 /* [AS] Initialize pv info list [HGM] and game state */
705 for( i=0; i<=framePtr; i++ ) {
706 pvInfoList[i].depth = -1;
707 boards[i][EP_STATUS] = EP_NONE;
708 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
713 * Parse timeControl resource
715 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
716 appData.movesPerSession)) {
718 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
719 DisplayFatalError(buf, 0, 2);
723 * Parse searchTime resource
725 if (*appData.searchTime != NULLCHAR) {
726 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
728 searchTime = min * 60;
729 } else if (matched == 2) {
730 searchTime = min * 60 + sec;
733 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
734 DisplayFatalError(buf, 0, 2);
738 /* [AS] Adjudication threshold */
739 adjudicateLossThreshold = appData.adjudicateLossThreshold;
741 first.which = "first";
742 second.which = "second";
743 first.maybeThinking = second.maybeThinking = FALSE;
744 first.pr = second.pr = NoProc;
745 first.isr = second.isr = NULL;
746 first.sendTime = second.sendTime = 2;
747 first.sendDrawOffers = 1;
748 if (appData.firstPlaysBlack) {
749 first.twoMachinesColor = "black\n";
750 second.twoMachinesColor = "white\n";
752 first.twoMachinesColor = "white\n";
753 second.twoMachinesColor = "black\n";
755 first.program = appData.firstChessProgram;
756 second.program = appData.secondChessProgram;
757 first.host = appData.firstHost;
758 second.host = appData.secondHost;
759 first.dir = appData.firstDirectory;
760 second.dir = appData.secondDirectory;
761 first.other = &second;
762 second.other = &first;
763 first.initString = appData.initString;
764 second.initString = appData.secondInitString;
765 first.computerString = appData.firstComputerString;
766 second.computerString = appData.secondComputerString;
767 first.useSigint = second.useSigint = TRUE;
768 first.useSigterm = second.useSigterm = TRUE;
769 first.reuse = appData.reuseFirst;
770 second.reuse = appData.reuseSecond;
771 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
772 second.nps = appData.secondNPS;
773 first.useSetboard = second.useSetboard = FALSE;
774 first.useSAN = second.useSAN = FALSE;
775 first.usePing = second.usePing = FALSE;
776 first.lastPing = second.lastPing = 0;
777 first.lastPong = second.lastPong = 0;
778 first.usePlayother = second.usePlayother = FALSE;
779 first.useColors = second.useColors = TRUE;
780 first.useUsermove = second.useUsermove = FALSE;
781 first.sendICS = second.sendICS = FALSE;
782 first.sendName = second.sendName = appData.icsActive;
783 first.sdKludge = second.sdKludge = FALSE;
784 first.stKludge = second.stKludge = FALSE;
785 TidyProgramName(first.program, first.host, first.tidy);
786 TidyProgramName(second.program, second.host, second.tidy);
787 first.matchWins = second.matchWins = 0;
788 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
789 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
790 first.analysisSupport = second.analysisSupport = 2; /* detect */
791 first.analyzing = second.analyzing = FALSE;
792 first.initDone = second.initDone = FALSE;
794 /* New features added by Tord: */
795 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
796 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
797 /* End of new features added by Tord. */
798 first.fenOverride = appData.fenOverride1;
799 second.fenOverride = appData.fenOverride2;
801 /* [HGM] time odds: set factor for each machine */
802 first.timeOdds = appData.firstTimeOdds;
803 second.timeOdds = appData.secondTimeOdds;
805 if(appData.timeOddsMode) {
806 norm = first.timeOdds;
807 if(norm > second.timeOdds) norm = second.timeOdds;
809 first.timeOdds /= norm;
810 second.timeOdds /= norm;
813 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
814 first.accumulateTC = appData.firstAccumulateTC;
815 second.accumulateTC = appData.secondAccumulateTC;
816 first.maxNrOfSessions = second.maxNrOfSessions = 1;
819 first.debug = second.debug = FALSE;
820 first.supportsNPS = second.supportsNPS = UNKNOWN;
823 first.optionSettings = appData.firstOptions;
824 second.optionSettings = appData.secondOptions;
826 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
827 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
828 first.isUCI = appData.firstIsUCI; /* [AS] */
829 second.isUCI = appData.secondIsUCI; /* [AS] */
830 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
831 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
833 if (appData.firstProtocolVersion > PROTOVER
834 || appData.firstProtocolVersion < 1)
839 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
840 appData.firstProtocolVersion);
841 if( (len > MSG_SIZ) && appData.debugMode )
842 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
844 DisplayFatalError(buf, 0, 2);
848 first.protocolVersion = appData.firstProtocolVersion;
851 if (appData.secondProtocolVersion > PROTOVER
852 || appData.secondProtocolVersion < 1)
857 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
858 appData.secondProtocolVersion);
859 if( (len > MSG_SIZ) && appData.debugMode )
860 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
862 DisplayFatalError(buf, 0, 2);
866 second.protocolVersion = appData.secondProtocolVersion;
869 if (appData.icsActive) {
870 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
871 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
872 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
873 appData.clockMode = FALSE;
874 first.sendTime = second.sendTime = 0;
878 /* Override some settings from environment variables, for backward
879 compatibility. Unfortunately it's not feasible to have the env
880 vars just set defaults, at least in xboard. Ugh.
882 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
887 if (appData.noChessProgram) {
888 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
889 sprintf(programVersion, "%s", PACKAGE_STRING);
891 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
892 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
893 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
896 if (!appData.icsActive) {
900 /* Check for variants that are supported only in ICS mode,
901 or not at all. Some that are accepted here nevertheless
902 have bugs; see comments below.
904 VariantClass variant = StringToVariant(appData.variant);
906 case VariantBughouse: /* need four players and two boards */
907 case VariantKriegspiel: /* need to hide pieces and move details */
908 /* case VariantFischeRandom: (Fabien: moved below) */
909 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
910 if( (len > MSG_SIZ) && appData.debugMode )
911 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
913 DisplayFatalError(buf, 0, 2);
917 case VariantLoadable:
927 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
928 if( (len > MSG_SIZ) && appData.debugMode )
929 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
931 DisplayFatalError(buf, 0, 2);
934 case VariantXiangqi: /* [HGM] repetition rules not implemented */
935 case VariantFairy: /* [HGM] TestLegality definitely off! */
936 case VariantGothic: /* [HGM] should work */
937 case VariantCapablanca: /* [HGM] should work */
938 case VariantCourier: /* [HGM] initial forced moves not implemented */
939 case VariantShogi: /* [HGM] could still mate with pawn drop */
940 case VariantKnightmate: /* [HGM] should work */
941 case VariantCylinder: /* [HGM] untested */
942 case VariantFalcon: /* [HGM] untested */
943 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
944 offboard interposition not understood */
945 case VariantNormal: /* definitely works! */
946 case VariantWildCastle: /* pieces not automatically shuffled */
947 case VariantNoCastle: /* pieces not automatically shuffled */
948 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
949 case VariantLosers: /* should work except for win condition,
950 and doesn't know captures are mandatory */
951 case VariantSuicide: /* should work except for win condition,
952 and doesn't know captures are mandatory */
953 case VariantGiveaway: /* should work except for win condition,
954 and doesn't know captures are mandatory */
955 case VariantTwoKings: /* should work */
956 case VariantAtomic: /* should work except for win condition */
957 case Variant3Check: /* should work except for win condition */
958 case VariantShatranj: /* should work except for all win conditions */
959 case VariantMakruk: /* should work except for daw countdown */
960 case VariantBerolina: /* might work if TestLegality is off */
961 case VariantCapaRandom: /* should work */
962 case VariantJanus: /* should work */
963 case VariantSuper: /* experimental */
964 case VariantGreat: /* experimental, requires legality testing to be off */
965 case VariantSChess: /* S-Chess, should work */
966 case VariantSpartan: /* should work */
971 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
972 InitEngineUCI( installDir, &second );
975 int NextIntegerFromString( char ** str, long * value )
980 while( *s == ' ' || *s == '\t' ) {
986 if( *s >= '0' && *s <= '9' ) {
987 while( *s >= '0' && *s <= '9' ) {
988 *value = *value * 10 + (*s - '0');
1000 int NextTimeControlFromString( char ** str, long * value )
1003 int result = NextIntegerFromString( str, &temp );
1006 *value = temp * 60; /* Minutes */
1007 if( **str == ':' ) {
1009 result = NextIntegerFromString( str, &temp );
1010 *value += temp; /* Seconds */
1017 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1018 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1019 int result = -1, type = 0; long temp, temp2;
1021 if(**str != ':') return -1; // old params remain in force!
1023 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1024 if( NextIntegerFromString( str, &temp ) ) return -1;
1025 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1028 /* time only: incremental or sudden-death time control */
1029 if(**str == '+') { /* increment follows; read it */
1031 if(**str == '!') type = *(*str)++; // Bronstein TC
1032 if(result = NextIntegerFromString( str, &temp2)) return -1;
1033 *inc = temp2 * 1000;
1034 if(**str == '.') { // read fraction of increment
1035 char *start = ++(*str);
1036 if(result = NextIntegerFromString( str, &temp2)) return -1;
1038 while(start++ < *str) temp2 /= 10;
1042 *moves = 0; *tc = temp * 1000; *incType = type;
1046 (*str)++; /* classical time control */
1047 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1058 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1059 { /* [HGM] get time to add from the multi-session time-control string */
1060 int incType, moves=1; /* kludge to force reading of first session */
1061 long time, increment;
1064 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1065 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1067 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1068 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1069 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1070 if(movenr == -1) return time; /* last move before new session */
1071 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1072 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1073 if(!moves) return increment; /* current session is incremental */
1074 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1075 } while(movenr >= -1); /* try again for next session */
1077 return 0; // no new time quota on this move
1081 ParseTimeControl(tc, ti, mps)
1088 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1091 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1092 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1093 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1097 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1099 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1102 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1104 snprintf(buf, MSG_SIZ, ":%s", mytc);
1106 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1108 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1113 /* Parse second time control */
1116 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1124 timeControl_2 = tc2 * 1000;
1134 timeControl = tc1 * 1000;
1137 timeIncrement = ti * 1000; /* convert to ms */
1138 movesPerSession = 0;
1141 movesPerSession = mps;
1149 if (appData.debugMode) {
1150 fprintf(debugFP, "%s\n", programVersion);
1153 set_cont_sequence(appData.wrapContSeq);
1154 if (appData.matchGames > 0) {
1155 appData.matchMode = TRUE;
1156 } else if (appData.matchMode) {
1157 appData.matchGames = 1;
1159 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1160 appData.matchGames = appData.sameColorGames;
1161 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1162 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1163 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1166 if (appData.noChessProgram || first.protocolVersion == 1) {
1169 /* kludge: allow timeout for initial "feature" commands */
1171 DisplayMessage("", _("Starting chess program"));
1172 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1177 InitBackEnd3 P((void))
1179 GameMode initialMode;
1183 InitChessProgram(&first, startedFromSetupPosition);
1185 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1186 free(programVersion);
1187 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1188 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1191 if (appData.icsActive) {
1193 /* [DM] Make a console window if needed [HGM] merged ifs */
1199 if (*appData.icsCommPort != NULLCHAR)
1200 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1201 appData.icsCommPort);
1203 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1204 appData.icsHost, appData.icsPort);
1206 if( (len > MSG_SIZ) && appData.debugMode )
1207 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1209 DisplayFatalError(buf, err, 1);
1214 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1216 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1217 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1218 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1219 } else if (appData.noChessProgram) {
1225 if (*appData.cmailGameName != NULLCHAR) {
1227 OpenLoopback(&cmailPR);
1229 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1233 DisplayMessage("", "");
1234 if (StrCaseCmp(appData.initialMode, "") == 0) {
1235 initialMode = BeginningOfGame;
1236 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1237 initialMode = TwoMachinesPlay;
1238 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1239 initialMode = AnalyzeFile;
1240 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1241 initialMode = AnalyzeMode;
1242 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1243 initialMode = MachinePlaysWhite;
1244 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1245 initialMode = MachinePlaysBlack;
1246 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1247 initialMode = EditGame;
1248 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1249 initialMode = EditPosition;
1250 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1251 initialMode = Training;
1253 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1254 if( (len > MSG_SIZ) && appData.debugMode )
1255 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1257 DisplayFatalError(buf, 0, 2);
1261 if (appData.matchMode) {
1262 /* Set up machine vs. machine match */
1263 if (appData.noChessProgram) {
1264 DisplayFatalError(_("Can't have a match with no chess programs"),
1270 if (*appData.loadGameFile != NULLCHAR) {
1271 int index = appData.loadGameIndex; // [HGM] autoinc
1272 if(index<0) lastIndex = index = 1;
1273 if (!LoadGameFromFile(appData.loadGameFile,
1275 appData.loadGameFile, FALSE)) {
1276 DisplayFatalError(_("Bad game file"), 0, 1);
1279 } else if (*appData.loadPositionFile != NULLCHAR) {
1280 int index = appData.loadPositionIndex; // [HGM] autoinc
1281 if(index<0) lastIndex = index = 1;
1282 if (!LoadPositionFromFile(appData.loadPositionFile,
1284 appData.loadPositionFile)) {
1285 DisplayFatalError(_("Bad position file"), 0, 1);
1290 } else if (*appData.cmailGameName != NULLCHAR) {
1291 /* Set up cmail mode */
1292 ReloadCmailMsgEvent(TRUE);
1294 /* Set up other modes */
1295 if (initialMode == AnalyzeFile) {
1296 if (*appData.loadGameFile == NULLCHAR) {
1297 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1301 if (*appData.loadGameFile != NULLCHAR) {
1302 (void) LoadGameFromFile(appData.loadGameFile,
1303 appData.loadGameIndex,
1304 appData.loadGameFile, TRUE);
1305 } else if (*appData.loadPositionFile != NULLCHAR) {
1306 (void) LoadPositionFromFile(appData.loadPositionFile,
1307 appData.loadPositionIndex,
1308 appData.loadPositionFile);
1309 /* [HGM] try to make self-starting even after FEN load */
1310 /* to allow automatic setup of fairy variants with wtm */
1311 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1312 gameMode = BeginningOfGame;
1313 setboardSpoiledMachineBlack = 1;
1315 /* [HGM] loadPos: make that every new game uses the setup */
1316 /* from file as long as we do not switch variant */
1317 if(!blackPlaysFirst) {
1318 startedFromPositionFile = TRUE;
1319 CopyBoard(filePosition, boards[0]);
1322 if (initialMode == AnalyzeMode) {
1323 if (appData.noChessProgram) {
1324 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1327 if (appData.icsActive) {
1328 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1332 } else if (initialMode == AnalyzeFile) {
1333 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1334 ShowThinkingEvent();
1336 AnalysisPeriodicEvent(1);
1337 } else if (initialMode == MachinePlaysWhite) {
1338 if (appData.noChessProgram) {
1339 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1343 if (appData.icsActive) {
1344 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1348 MachineWhiteEvent();
1349 } else if (initialMode == MachinePlaysBlack) {
1350 if (appData.noChessProgram) {
1351 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1355 if (appData.icsActive) {
1356 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1360 MachineBlackEvent();
1361 } else if (initialMode == TwoMachinesPlay) {
1362 if (appData.noChessProgram) {
1363 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1367 if (appData.icsActive) {
1368 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1373 } else if (initialMode == EditGame) {
1375 } else if (initialMode == EditPosition) {
1376 EditPositionEvent();
1377 } else if (initialMode == Training) {
1378 if (*appData.loadGameFile == NULLCHAR) {
1379 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1388 * Establish will establish a contact to a remote host.port.
1389 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1390 * used to talk to the host.
1391 * Returns 0 if okay, error code if not.
1398 if (*appData.icsCommPort != NULLCHAR) {
1399 /* Talk to the host through a serial comm port */
1400 return OpenCommPort(appData.icsCommPort, &icsPR);
1402 } else if (*appData.gateway != NULLCHAR) {
1403 if (*appData.remoteShell == NULLCHAR) {
1404 /* Use the rcmd protocol to run telnet program on a gateway host */
1405 snprintf(buf, sizeof(buf), "%s %s %s",
1406 appData.telnetProgram, appData.icsHost, appData.icsPort);
1407 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1410 /* Use the rsh program to run telnet program on a gateway host */
1411 if (*appData.remoteUser == NULLCHAR) {
1412 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1413 appData.gateway, appData.telnetProgram,
1414 appData.icsHost, appData.icsPort);
1416 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1417 appData.remoteShell, appData.gateway,
1418 appData.remoteUser, appData.telnetProgram,
1419 appData.icsHost, appData.icsPort);
1421 return StartChildProcess(buf, "", &icsPR);
1424 } else if (appData.useTelnet) {
1425 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1428 /* TCP socket interface differs somewhat between
1429 Unix and NT; handle details in the front end.
1431 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1435 void EscapeExpand(char *p, char *q)
1436 { // [HGM] initstring: routine to shape up string arguments
1437 while(*p++ = *q++) if(p[-1] == '\\')
1439 case 'n': p[-1] = '\n'; break;
1440 case 'r': p[-1] = '\r'; break;
1441 case 't': p[-1] = '\t'; break;
1442 case '\\': p[-1] = '\\'; break;
1443 case 0: *p = 0; return;
1444 default: p[-1] = q[-1]; break;
1449 show_bytes(fp, buf, count)
1455 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1456 fprintf(fp, "\\%03o", *buf & 0xff);
1465 /* Returns an errno value */
1467 OutputMaybeTelnet(pr, message, count, outError)
1473 char buf[8192], *p, *q, *buflim;
1474 int left, newcount, outcount;
1476 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1477 *appData.gateway != NULLCHAR) {
1478 if (appData.debugMode) {
1479 fprintf(debugFP, ">ICS: ");
1480 show_bytes(debugFP, message, count);
1481 fprintf(debugFP, "\n");
1483 return OutputToProcess(pr, message, count, outError);
1486 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1493 if (appData.debugMode) {
1494 fprintf(debugFP, ">ICS: ");
1495 show_bytes(debugFP, buf, newcount);
1496 fprintf(debugFP, "\n");
1498 outcount = OutputToProcess(pr, buf, newcount, outError);
1499 if (outcount < newcount) return -1; /* to be sure */
1506 } else if (((unsigned char) *p) == TN_IAC) {
1507 *q++ = (char) TN_IAC;
1514 if (appData.debugMode) {
1515 fprintf(debugFP, ">ICS: ");
1516 show_bytes(debugFP, buf, newcount);
1517 fprintf(debugFP, "\n");
1519 outcount = OutputToProcess(pr, buf, newcount, outError);
1520 if (outcount < newcount) return -1; /* to be sure */
1525 read_from_player(isr, closure, message, count, error)
1532 int outError, outCount;
1533 static int gotEof = 0;
1535 /* Pass data read from player on to ICS */
1538 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1539 if (outCount < count) {
1540 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1542 } else if (count < 0) {
1543 RemoveInputSource(isr);
1544 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1545 } else if (gotEof++ > 0) {
1546 RemoveInputSource(isr);
1547 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1553 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1554 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1555 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1556 SendToICS("date\n");
1557 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1560 /* added routine for printf style output to ics */
1561 void ics_printf(char *format, ...)
1563 char buffer[MSG_SIZ];
1566 va_start(args, format);
1567 vsnprintf(buffer, sizeof(buffer), format, args);
1568 buffer[sizeof(buffer)-1] = '\0';
1577 int count, outCount, outError;
1579 if (icsPR == NULL) return;
1582 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1583 if (outCount < count) {
1584 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1588 /* This is used for sending logon scripts to the ICS. Sending
1589 without a delay causes problems when using timestamp on ICC
1590 (at least on my machine). */
1592 SendToICSDelayed(s,msdelay)
1596 int count, outCount, outError;
1598 if (icsPR == NULL) return;
1601 if (appData.debugMode) {
1602 fprintf(debugFP, ">ICS: ");
1603 show_bytes(debugFP, s, count);
1604 fprintf(debugFP, "\n");
1606 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1608 if (outCount < count) {
1609 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1614 /* Remove all highlighting escape sequences in s
1615 Also deletes any suffix starting with '('
1618 StripHighlightAndTitle(s)
1621 static char retbuf[MSG_SIZ];
1624 while (*s != NULLCHAR) {
1625 while (*s == '\033') {
1626 while (*s != NULLCHAR && !isalpha(*s)) s++;
1627 if (*s != NULLCHAR) s++;
1629 while (*s != NULLCHAR && *s != '\033') {
1630 if (*s == '(' || *s == '[') {
1641 /* Remove all highlighting escape sequences in s */
1646 static char retbuf[MSG_SIZ];
1649 while (*s != NULLCHAR) {
1650 while (*s == '\033') {
1651 while (*s != NULLCHAR && !isalpha(*s)) s++;
1652 if (*s != NULLCHAR) s++;
1654 while (*s != NULLCHAR && *s != '\033') {
1662 char *variantNames[] = VARIANT_NAMES;
1667 return variantNames[v];
1671 /* Identify a variant from the strings the chess servers use or the
1672 PGN Variant tag names we use. */
1679 VariantClass v = VariantNormal;
1680 int i, found = FALSE;
1686 /* [HGM] skip over optional board-size prefixes */
1687 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1688 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1689 while( *e++ != '_');
1692 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1696 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1697 if (StrCaseStr(e, variantNames[i])) {
1698 v = (VariantClass) i;
1705 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1706 || StrCaseStr(e, "wild/fr")
1707 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1708 v = VariantFischeRandom;
1709 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1710 (i = 1, p = StrCaseStr(e, "w"))) {
1712 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1719 case 0: /* FICS only, actually */
1721 /* Castling legal even if K starts on d-file */
1722 v = VariantWildCastle;
1727 /* Castling illegal even if K & R happen to start in
1728 normal positions. */
1729 v = VariantNoCastle;
1742 /* Castling legal iff K & R start in normal positions */
1748 /* Special wilds for position setup; unclear what to do here */
1749 v = VariantLoadable;
1752 /* Bizarre ICC game */
1753 v = VariantTwoKings;
1756 v = VariantKriegspiel;
1762 v = VariantFischeRandom;
1765 v = VariantCrazyhouse;
1768 v = VariantBughouse;
1774 /* Not quite the same as FICS suicide! */
1775 v = VariantGiveaway;
1781 v = VariantShatranj;
1784 /* Temporary names for future ICC types. The name *will* change in
1785 the next xboard/WinBoard release after ICC defines it. */
1823 v = VariantCapablanca;
1826 v = VariantKnightmate;
1832 v = VariantCylinder;
1838 v = VariantCapaRandom;
1841 v = VariantBerolina;
1853 /* Found "wild" or "w" in the string but no number;
1854 must assume it's normal chess. */
1858 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1859 if( (len > MSG_SIZ) && appData.debugMode )
1860 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1862 DisplayError(buf, 0);
1868 if (appData.debugMode) {
1869 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1870 e, wnum, VariantName(v));
1875 static int leftover_start = 0, leftover_len = 0;
1876 char star_match[STAR_MATCH_N][MSG_SIZ];
1878 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1879 advance *index beyond it, and set leftover_start to the new value of
1880 *index; else return FALSE. If pattern contains the character '*', it
1881 matches any sequence of characters not containing '\r', '\n', or the
1882 character following the '*' (if any), and the matched sequence(s) are
1883 copied into star_match.
1886 looking_at(buf, index, pattern)
1891 char *bufp = &buf[*index], *patternp = pattern;
1893 char *matchp = star_match[0];
1896 if (*patternp == NULLCHAR) {
1897 *index = leftover_start = bufp - buf;
1901 if (*bufp == NULLCHAR) return FALSE;
1902 if (*patternp == '*') {
1903 if (*bufp == *(patternp + 1)) {
1905 matchp = star_match[++star_count];
1909 } else if (*bufp == '\n' || *bufp == '\r') {
1911 if (*patternp == NULLCHAR)
1916 *matchp++ = *bufp++;
1920 if (*patternp != *bufp) return FALSE;
1927 SendToPlayer(data, length)
1931 int error, outCount;
1932 outCount = OutputToProcess(NoProc, data, length, &error);
1933 if (outCount < length) {
1934 DisplayFatalError(_("Error writing to display"), error, 1);
1939 PackHolding(packed, holding)
1951 switch (runlength) {
1962 sprintf(q, "%d", runlength);
1974 /* Telnet protocol requests from the front end */
1976 TelnetRequest(ddww, option)
1977 unsigned char ddww, option;
1979 unsigned char msg[3];
1980 int outCount, outError;
1982 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1984 if (appData.debugMode) {
1985 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2001 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2010 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2013 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2018 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2020 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2027 if (!appData.icsActive) return;
2028 TelnetRequest(TN_DO, TN_ECHO);
2034 if (!appData.icsActive) return;
2035 TelnetRequest(TN_DONT, TN_ECHO);
2039 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2041 /* put the holdings sent to us by the server on the board holdings area */
2042 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2046 if(gameInfo.holdingsWidth < 2) return;
2047 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2048 return; // prevent overwriting by pre-board holdings
2050 if( (int)lowestPiece >= BlackPawn ) {
2053 holdingsStartRow = BOARD_HEIGHT-1;
2056 holdingsColumn = BOARD_WIDTH-1;
2057 countsColumn = BOARD_WIDTH-2;
2058 holdingsStartRow = 0;
2062 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2063 board[i][holdingsColumn] = EmptySquare;
2064 board[i][countsColumn] = (ChessSquare) 0;
2066 while( (p=*holdings++) != NULLCHAR ) {
2067 piece = CharToPiece( ToUpper(p) );
2068 if(piece == EmptySquare) continue;
2069 /*j = (int) piece - (int) WhitePawn;*/
2070 j = PieceToNumber(piece);
2071 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2072 if(j < 0) continue; /* should not happen */
2073 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2074 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2075 board[holdingsStartRow+j*direction][countsColumn]++;
2081 VariantSwitch(Board board, VariantClass newVariant)
2083 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2084 static Board oldBoard;
2086 startedFromPositionFile = FALSE;
2087 if(gameInfo.variant == newVariant) return;
2089 /* [HGM] This routine is called each time an assignment is made to
2090 * gameInfo.variant during a game, to make sure the board sizes
2091 * are set to match the new variant. If that means adding or deleting
2092 * holdings, we shift the playing board accordingly
2093 * This kludge is needed because in ICS observe mode, we get boards
2094 * of an ongoing game without knowing the variant, and learn about the
2095 * latter only later. This can be because of the move list we requested,
2096 * in which case the game history is refilled from the beginning anyway,
2097 * but also when receiving holdings of a crazyhouse game. In the latter
2098 * case we want to add those holdings to the already received position.
2102 if (appData.debugMode) {
2103 fprintf(debugFP, "Switch board from %s to %s\n",
2104 VariantName(gameInfo.variant), VariantName(newVariant));
2105 setbuf(debugFP, NULL);
2107 shuffleOpenings = 0; /* [HGM] shuffle */
2108 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2112 newWidth = 9; newHeight = 9;
2113 gameInfo.holdingsSize = 7;
2114 case VariantBughouse:
2115 case VariantCrazyhouse:
2116 newHoldingsWidth = 2; break;
2120 newHoldingsWidth = 2;
2121 gameInfo.holdingsSize = 8;
2124 case VariantCapablanca:
2125 case VariantCapaRandom:
2128 newHoldingsWidth = gameInfo.holdingsSize = 0;
2131 if(newWidth != gameInfo.boardWidth ||
2132 newHeight != gameInfo.boardHeight ||
2133 newHoldingsWidth != gameInfo.holdingsWidth ) {
2135 /* shift position to new playing area, if needed */
2136 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2137 for(i=0; i<BOARD_HEIGHT; i++)
2138 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2139 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2141 for(i=0; i<newHeight; i++) {
2142 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2143 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2145 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2146 for(i=0; i<BOARD_HEIGHT; i++)
2147 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2148 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2151 gameInfo.boardWidth = newWidth;
2152 gameInfo.boardHeight = newHeight;
2153 gameInfo.holdingsWidth = newHoldingsWidth;
2154 gameInfo.variant = newVariant;
2155 InitDrawingSizes(-2, 0);
2156 } else gameInfo.variant = newVariant;
2157 CopyBoard(oldBoard, board); // remember correctly formatted board
2158 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2159 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2162 static int loggedOn = FALSE;
2164 /*-- Game start info cache: --*/
2166 char gs_kind[MSG_SIZ];
2167 static char player1Name[128] = "";
2168 static char player2Name[128] = "";
2169 static char cont_seq[] = "\n\\ ";
2170 static int player1Rating = -1;
2171 static int player2Rating = -1;
2172 /*----------------------------*/
2174 ColorClass curColor = ColorNormal;
2175 int suppressKibitz = 0;
2178 Boolean soughtPending = FALSE;
2179 Boolean seekGraphUp;
2180 #define MAX_SEEK_ADS 200
2182 char *seekAdList[MAX_SEEK_ADS];
2183 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2184 float tcList[MAX_SEEK_ADS];
2185 char colorList[MAX_SEEK_ADS];
2186 int nrOfSeekAds = 0;
2187 int minRating = 1010, maxRating = 2800;
2188 int hMargin = 10, vMargin = 20, h, w;
2189 extern int squareSize, lineGap;
2194 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2195 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2196 if(r < minRating+100 && r >=0 ) r = minRating+100;
2197 if(r > maxRating) r = maxRating;
2198 if(tc < 1.) tc = 1.;
2199 if(tc > 95.) tc = 95.;
2200 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2201 y = ((double)r - minRating)/(maxRating - minRating)
2202 * (h-vMargin-squareSize/8-1) + vMargin;
2203 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2204 if(strstr(seekAdList[i], " u ")) color = 1;
2205 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2206 !strstr(seekAdList[i], "bullet") &&
2207 !strstr(seekAdList[i], "blitz") &&
2208 !strstr(seekAdList[i], "standard") ) color = 2;
2209 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2210 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2214 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2216 char buf[MSG_SIZ], *ext = "";
2217 VariantClass v = StringToVariant(type);
2218 if(strstr(type, "wild")) {
2219 ext = type + 4; // append wild number
2220 if(v == VariantFischeRandom) type = "chess960"; else
2221 if(v == VariantLoadable) type = "setup"; else
2222 type = VariantName(v);
2224 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2225 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2226 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2227 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2228 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2229 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2230 seekNrList[nrOfSeekAds] = nr;
2231 zList[nrOfSeekAds] = 0;
2232 seekAdList[nrOfSeekAds++] = StrSave(buf);
2233 if(plot) PlotSeekAd(nrOfSeekAds-1);
2240 int x = xList[i], y = yList[i], d=squareSize/4, k;
2241 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2242 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2243 // now replot every dot that overlapped
2244 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2245 int xx = xList[k], yy = yList[k];
2246 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2247 DrawSeekDot(xx, yy, colorList[k]);
2252 RemoveSeekAd(int nr)
2255 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2257 if(seekAdList[i]) free(seekAdList[i]);
2258 seekAdList[i] = seekAdList[--nrOfSeekAds];
2259 seekNrList[i] = seekNrList[nrOfSeekAds];
2260 ratingList[i] = ratingList[nrOfSeekAds];
2261 colorList[i] = colorList[nrOfSeekAds];
2262 tcList[i] = tcList[nrOfSeekAds];
2263 xList[i] = xList[nrOfSeekAds];
2264 yList[i] = yList[nrOfSeekAds];
2265 zList[i] = zList[nrOfSeekAds];
2266 seekAdList[nrOfSeekAds] = NULL;
2272 MatchSoughtLine(char *line)
2274 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2275 int nr, base, inc, u=0; char dummy;
2277 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2278 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2280 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2281 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2282 // match: compact and save the line
2283 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2293 if(!seekGraphUp) return FALSE;
2294 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2295 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2297 DrawSeekBackground(0, 0, w, h);
2298 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2299 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2300 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2301 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2303 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2306 snprintf(buf, MSG_SIZ, "%d", i);
2307 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2310 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2311 for(i=1; i<100; i+=(i<10?1:5)) {
2312 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2313 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2314 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2316 snprintf(buf, MSG_SIZ, "%d", i);
2317 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2320 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2324 int SeekGraphClick(ClickType click, int x, int y, int moving)
2326 static int lastDown = 0, displayed = 0, lastSecond;
2327 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2328 if(click == Release || moving) return FALSE;
2330 soughtPending = TRUE;
2331 SendToICS(ics_prefix);
2332 SendToICS("sought\n"); // should this be "sought all"?
2333 } else { // issue challenge based on clicked ad
2334 int dist = 10000; int i, closest = 0, second = 0;
2335 for(i=0; i<nrOfSeekAds; i++) {
2336 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2337 if(d < dist) { dist = d; closest = i; }
2338 second += (d - zList[i] < 120); // count in-range ads
2339 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2343 second = (second > 1);
2344 if(displayed != closest || second != lastSecond) {
2345 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2346 lastSecond = second; displayed = closest;
2348 if(click == Press) {
2349 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2352 } // on press 'hit', only show info
2353 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2354 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2355 SendToICS(ics_prefix);
2357 return TRUE; // let incoming board of started game pop down the graph
2358 } else if(click == Release) { // release 'miss' is ignored
2359 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2360 if(moving == 2) { // right up-click
2361 nrOfSeekAds = 0; // refresh graph
2362 soughtPending = TRUE;
2363 SendToICS(ics_prefix);
2364 SendToICS("sought\n"); // should this be "sought all"?
2367 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2368 // press miss or release hit 'pop down' seek graph
2369 seekGraphUp = FALSE;
2370 DrawPosition(TRUE, NULL);
2376 read_from_ics(isr, closure, data, count, error)
2383 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2384 #define STARTED_NONE 0
2385 #define STARTED_MOVES 1
2386 #define STARTED_BOARD 2
2387 #define STARTED_OBSERVE 3
2388 #define STARTED_HOLDINGS 4
2389 #define STARTED_CHATTER 5
2390 #define STARTED_COMMENT 6
2391 #define STARTED_MOVES_NOHIDE 7
2393 static int started = STARTED_NONE;
2394 static char parse[20000];
2395 static int parse_pos = 0;
2396 static char buf[BUF_SIZE + 1];
2397 static int firstTime = TRUE, intfSet = FALSE;
2398 static ColorClass prevColor = ColorNormal;
2399 static int savingComment = FALSE;
2400 static int cmatch = 0; // continuation sequence match
2407 int backup; /* [DM] For zippy color lines */
2409 char talker[MSG_SIZ]; // [HGM] chat
2412 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2414 if (appData.debugMode) {
2416 fprintf(debugFP, "<ICS: ");
2417 show_bytes(debugFP, data, count);
2418 fprintf(debugFP, "\n");
2422 if (appData.debugMode) { int f = forwardMostMove;
2423 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2424 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2425 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2428 /* If last read ended with a partial line that we couldn't parse,
2429 prepend it to the new read and try again. */
2430 if (leftover_len > 0) {
2431 for (i=0; i<leftover_len; i++)
2432 buf[i] = buf[leftover_start + i];
2435 /* copy new characters into the buffer */
2436 bp = buf + leftover_len;
2437 buf_len=leftover_len;
2438 for (i=0; i<count; i++)
2441 if (data[i] == '\r')
2444 // join lines split by ICS?
2445 if (!appData.noJoin)
2448 Joining just consists of finding matches against the
2449 continuation sequence, and discarding that sequence
2450 if found instead of copying it. So, until a match
2451 fails, there's nothing to do since it might be the
2452 complete sequence, and thus, something we don't want
2455 if (data[i] == cont_seq[cmatch])
2458 if (cmatch == strlen(cont_seq))
2460 cmatch = 0; // complete match. just reset the counter
2463 it's possible for the ICS to not include the space
2464 at the end of the last word, making our [correct]
2465 join operation fuse two separate words. the server
2466 does this when the space occurs at the width setting.
2468 if (!buf_len || buf[buf_len-1] != ' ')
2479 match failed, so we have to copy what matched before
2480 falling through and copying this character. In reality,
2481 this will only ever be just the newline character, but
2482 it doesn't hurt to be precise.
2484 strncpy(bp, cont_seq, cmatch);
2496 buf[buf_len] = NULLCHAR;
2497 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2502 while (i < buf_len) {
2503 /* Deal with part of the TELNET option negotiation
2504 protocol. We refuse to do anything beyond the
2505 defaults, except that we allow the WILL ECHO option,
2506 which ICS uses to turn off password echoing when we are
2507 directly connected to it. We reject this option
2508 if localLineEditing mode is on (always on in xboard)
2509 and we are talking to port 23, which might be a real
2510 telnet server that will try to keep WILL ECHO on permanently.
2512 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2513 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2514 unsigned char option;
2516 switch ((unsigned char) buf[++i]) {
2518 if (appData.debugMode)
2519 fprintf(debugFP, "\n<WILL ");
2520 switch (option = (unsigned char) buf[++i]) {
2522 if (appData.debugMode)
2523 fprintf(debugFP, "ECHO ");
2524 /* Reply only if this is a change, according
2525 to the protocol rules. */
2526 if (remoteEchoOption) break;
2527 if (appData.localLineEditing &&
2528 atoi(appData.icsPort) == TN_PORT) {
2529 TelnetRequest(TN_DONT, TN_ECHO);
2532 TelnetRequest(TN_DO, TN_ECHO);
2533 remoteEchoOption = TRUE;
2537 if (appData.debugMode)
2538 fprintf(debugFP, "%d ", option);
2539 /* Whatever this is, we don't want it. */
2540 TelnetRequest(TN_DONT, option);
2545 if (appData.debugMode)
2546 fprintf(debugFP, "\n<WONT ");
2547 switch (option = (unsigned char) buf[++i]) {
2549 if (appData.debugMode)
2550 fprintf(debugFP, "ECHO ");
2551 /* Reply only if this is a change, according
2552 to the protocol rules. */
2553 if (!remoteEchoOption) break;
2555 TelnetRequest(TN_DONT, TN_ECHO);
2556 remoteEchoOption = FALSE;
2559 if (appData.debugMode)
2560 fprintf(debugFP, "%d ", (unsigned char) option);
2561 /* Whatever this is, it must already be turned
2562 off, because we never agree to turn on
2563 anything non-default, so according to the
2564 protocol rules, we don't reply. */
2569 if (appData.debugMode)
2570 fprintf(debugFP, "\n<DO ");
2571 switch (option = (unsigned char) buf[++i]) {
2573 /* Whatever this is, we refuse to do it. */
2574 if (appData.debugMode)
2575 fprintf(debugFP, "%d ", option);
2576 TelnetRequest(TN_WONT, option);
2581 if (appData.debugMode)
2582 fprintf(debugFP, "\n<DONT ");
2583 switch (option = (unsigned char) buf[++i]) {
2585 if (appData.debugMode)
2586 fprintf(debugFP, "%d ", option);
2587 /* Whatever this is, we are already not doing
2588 it, because we never agree to do anything
2589 non-default, so according to the protocol
2590 rules, we don't reply. */
2595 if (appData.debugMode)
2596 fprintf(debugFP, "\n<IAC ");
2597 /* Doubled IAC; pass it through */
2601 if (appData.debugMode)
2602 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2603 /* Drop all other telnet commands on the floor */
2606 if (oldi > next_out)
2607 SendToPlayer(&buf[next_out], oldi - next_out);
2613 /* OK, this at least will *usually* work */
2614 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2618 if (loggedOn && !intfSet) {
2619 if (ics_type == ICS_ICC) {
2620 snprintf(str, MSG_SIZ,
2621 "/set-quietly interface %s\n/set-quietly style 12\n",
2623 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2624 strcat(str, "/set-2 51 1\n/set seek 1\n");
2625 } else if (ics_type == ICS_CHESSNET) {
2626 snprintf(str, MSG_SIZ, "/style 12\n");
2628 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2629 strcat(str, programVersion);
2630 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2631 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2632 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2634 strcat(str, "$iset nohighlight 1\n");
2636 strcat(str, "$iset lock 1\n$style 12\n");
2639 NotifyFrontendLogin();
2643 if (started == STARTED_COMMENT) {
2644 /* Accumulate characters in comment */
2645 parse[parse_pos++] = buf[i];
2646 if (buf[i] == '\n') {
2647 parse[parse_pos] = NULLCHAR;
2648 if(chattingPartner>=0) {
2650 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2651 OutputChatMessage(chattingPartner, mess);
2652 chattingPartner = -1;
2653 next_out = i+1; // [HGM] suppress printing in ICS window
2655 if(!suppressKibitz) // [HGM] kibitz
2656 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2657 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2658 int nrDigit = 0, nrAlph = 0, j;
2659 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2660 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2661 parse[parse_pos] = NULLCHAR;
2662 // try to be smart: if it does not look like search info, it should go to
2663 // ICS interaction window after all, not to engine-output window.
2664 for(j=0; j<parse_pos; j++) { // count letters and digits
2665 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2666 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2667 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2669 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2670 int depth=0; float score;
2671 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2672 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2673 pvInfoList[forwardMostMove-1].depth = depth;
2674 pvInfoList[forwardMostMove-1].score = 100*score;
2676 OutputKibitz(suppressKibitz, parse);
2679 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2680 SendToPlayer(tmp, strlen(tmp));
2682 next_out = i+1; // [HGM] suppress printing in ICS window
2684 started = STARTED_NONE;
2686 /* Don't match patterns against characters in comment */
2691 if (started == STARTED_CHATTER) {
2692 if (buf[i] != '\n') {
2693 /* Don't match patterns against characters in chatter */
2697 started = STARTED_NONE;
2698 if(suppressKibitz) next_out = i+1;
2701 /* Kludge to deal with rcmd protocol */
2702 if (firstTime && looking_at(buf, &i, "\001*")) {
2703 DisplayFatalError(&buf[1], 0, 1);
2709 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2712 if (appData.debugMode)
2713 fprintf(debugFP, "ics_type %d\n", ics_type);
2716 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2717 ics_type = ICS_FICS;
2719 if (appData.debugMode)
2720 fprintf(debugFP, "ics_type %d\n", ics_type);
2723 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2724 ics_type = ICS_CHESSNET;
2726 if (appData.debugMode)
2727 fprintf(debugFP, "ics_type %d\n", ics_type);
2732 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2733 looking_at(buf, &i, "Logging you in as \"*\"") ||
2734 looking_at(buf, &i, "will be \"*\""))) {
2735 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2739 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2741 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2742 DisplayIcsInteractionTitle(buf);
2743 have_set_title = TRUE;
2746 /* skip finger notes */
2747 if (started == STARTED_NONE &&
2748 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2749 (buf[i] == '1' && buf[i+1] == '0')) &&
2750 buf[i+2] == ':' && buf[i+3] == ' ') {
2751 started = STARTED_CHATTER;
2757 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2758 if(appData.seekGraph) {
2759 if(soughtPending && MatchSoughtLine(buf+i)) {
2760 i = strstr(buf+i, "rated") - buf;
2761 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2762 next_out = leftover_start = i;
2763 started = STARTED_CHATTER;
2764 suppressKibitz = TRUE;
2767 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2768 && looking_at(buf, &i, "* ads displayed")) {
2769 soughtPending = FALSE;
2774 if(appData.autoRefresh) {
2775 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2776 int s = (ics_type == ICS_ICC); // ICC format differs
2778 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2779 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2780 looking_at(buf, &i, "*% "); // eat prompt
2781 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2782 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2783 next_out = i; // suppress
2786 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2787 char *p = star_match[0];
2789 if(seekGraphUp) RemoveSeekAd(atoi(p));
2790 while(*p && *p++ != ' '); // next
2792 looking_at(buf, &i, "*% "); // eat prompt
2793 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2800 /* skip formula vars */
2801 if (started == STARTED_NONE &&
2802 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2803 started = STARTED_CHATTER;
2808 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2809 if (appData.autoKibitz && started == STARTED_NONE &&
2810 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2811 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2812 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2813 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2814 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2815 suppressKibitz = TRUE;
2816 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2818 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2819 && (gameMode == IcsPlayingWhite)) ||
2820 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2821 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2822 started = STARTED_CHATTER; // own kibitz we simply discard
2824 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2825 parse_pos = 0; parse[0] = NULLCHAR;
2826 savingComment = TRUE;
2827 suppressKibitz = gameMode != IcsObserving ? 2 :
2828 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2832 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2833 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2834 && atoi(star_match[0])) {
2835 // suppress the acknowledgements of our own autoKibitz
2837 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2838 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2839 SendToPlayer(star_match[0], strlen(star_match[0]));
2840 if(looking_at(buf, &i, "*% ")) // eat prompt
2841 suppressKibitz = FALSE;
2845 } // [HGM] kibitz: end of patch
2847 // [HGM] chat: intercept tells by users for which we have an open chat window
2849 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2850 looking_at(buf, &i, "* whispers:") ||
2851 looking_at(buf, &i, "* kibitzes:") ||
2852 looking_at(buf, &i, "* shouts:") ||
2853 looking_at(buf, &i, "* c-shouts:") ||
2854 looking_at(buf, &i, "--> * ") ||
2855 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2856 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2857 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2858 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2860 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2861 chattingPartner = -1;
2863 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2864 for(p=0; p<MAX_CHAT; p++) {
2865 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2866 talker[0] = '['; strcat(talker, "] ");
2867 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2868 chattingPartner = p; break;
2871 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2872 for(p=0; p<MAX_CHAT; p++) {
2873 if(!strcmp("kibitzes", chatPartner[p])) {
2874 talker[0] = '['; strcat(talker, "] ");
2875 chattingPartner = p; break;
2878 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2879 for(p=0; p<MAX_CHAT; p++) {
2880 if(!strcmp("whispers", chatPartner[p])) {
2881 talker[0] = '['; strcat(talker, "] ");
2882 chattingPartner = p; break;
2885 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2886 if(buf[i-8] == '-' && buf[i-3] == 't')
2887 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2888 if(!strcmp("c-shouts", chatPartner[p])) {
2889 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2890 chattingPartner = p; break;
2893 if(chattingPartner < 0)
2894 for(p=0; p<MAX_CHAT; p++) {
2895 if(!strcmp("shouts", chatPartner[p])) {
2896 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2897 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2898 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2899 chattingPartner = p; break;
2903 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2904 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2905 talker[0] = 0; Colorize(ColorTell, FALSE);
2906 chattingPartner = p; break;
2908 if(chattingPartner<0) i = oldi; else {
2909 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2910 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2911 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2912 started = STARTED_COMMENT;
2913 parse_pos = 0; parse[0] = NULLCHAR;
2914 savingComment = 3 + chattingPartner; // counts as TRUE
2915 suppressKibitz = TRUE;
2918 } // [HGM] chat: end of patch
2920 if (appData.zippyTalk || appData.zippyPlay) {
2921 /* [DM] Backup address for color zippy lines */
2924 if (loggedOn == TRUE)
2925 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2926 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2928 } // [DM] 'else { ' deleted
2930 /* Regular tells and says */
2931 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2932 looking_at(buf, &i, "* (your partner) tells you: ") ||
2933 looking_at(buf, &i, "* says: ") ||
2934 /* Don't color "message" or "messages" output */
2935 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2936 looking_at(buf, &i, "*. * at *:*: ") ||
2937 looking_at(buf, &i, "--* (*:*): ") ||
2938 /* Message notifications (same color as tells) */
2939 looking_at(buf, &i, "* has left a message ") ||
2940 looking_at(buf, &i, "* just sent you a message:\n") ||
2941 /* Whispers and kibitzes */
2942 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2943 looking_at(buf, &i, "* kibitzes: ") ||
2945 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2947 if (tkind == 1 && strchr(star_match[0], ':')) {
2948 /* Avoid "tells you:" spoofs in channels */
2951 if (star_match[0][0] == NULLCHAR ||
2952 strchr(star_match[0], ' ') ||
2953 (tkind == 3 && strchr(star_match[1], ' '))) {
2954 /* Reject bogus matches */
2957 if (appData.colorize) {
2958 if (oldi > next_out) {
2959 SendToPlayer(&buf[next_out], oldi - next_out);
2964 Colorize(ColorTell, FALSE);
2965 curColor = ColorTell;
2968 Colorize(ColorKibitz, FALSE);
2969 curColor = ColorKibitz;
2972 p = strrchr(star_match[1], '(');
2979 Colorize(ColorChannel1, FALSE);
2980 curColor = ColorChannel1;
2982 Colorize(ColorChannel, FALSE);
2983 curColor = ColorChannel;
2987 curColor = ColorNormal;
2991 if (started == STARTED_NONE && appData.autoComment &&
2992 (gameMode == IcsObserving ||
2993 gameMode == IcsPlayingWhite ||
2994 gameMode == IcsPlayingBlack)) {
2995 parse_pos = i - oldi;
2996 memcpy(parse, &buf[oldi], parse_pos);
2997 parse[parse_pos] = NULLCHAR;
2998 started = STARTED_COMMENT;
2999 savingComment = TRUE;
3001 started = STARTED_CHATTER;
3002 savingComment = FALSE;
3009 if (looking_at(buf, &i, "* s-shouts: ") ||
3010 looking_at(buf, &i, "* c-shouts: ")) {
3011 if (appData.colorize) {
3012 if (oldi > next_out) {
3013 SendToPlayer(&buf[next_out], oldi - next_out);
3016 Colorize(ColorSShout, FALSE);
3017 curColor = ColorSShout;
3020 started = STARTED_CHATTER;
3024 if (looking_at(buf, &i, "--->")) {
3029 if (looking_at(buf, &i, "* shouts: ") ||
3030 looking_at(buf, &i, "--> ")) {
3031 if (appData.colorize) {
3032 if (oldi > next_out) {
3033 SendToPlayer(&buf[next_out], oldi - next_out);
3036 Colorize(ColorShout, FALSE);
3037 curColor = ColorShout;
3040 started = STARTED_CHATTER;
3044 if (looking_at( buf, &i, "Challenge:")) {
3045 if (appData.colorize) {
3046 if (oldi > next_out) {
3047 SendToPlayer(&buf[next_out], oldi - next_out);
3050 Colorize(ColorChallenge, FALSE);
3051 curColor = ColorChallenge;
3057 if (looking_at(buf, &i, "* offers you") ||
3058 looking_at(buf, &i, "* offers to be") ||
3059 looking_at(buf, &i, "* would like to") ||
3060 looking_at(buf, &i, "* requests to") ||
3061 looking_at(buf, &i, "Your opponent offers") ||
3062 looking_at(buf, &i, "Your opponent requests")) {
3064 if (appData.colorize) {
3065 if (oldi > next_out) {
3066 SendToPlayer(&buf[next_out], oldi - next_out);
3069 Colorize(ColorRequest, FALSE);
3070 curColor = ColorRequest;
3075 if (looking_at(buf, &i, "* (*) seeking")) {
3076 if (appData.colorize) {
3077 if (oldi > next_out) {
3078 SendToPlayer(&buf[next_out], oldi - next_out);
3081 Colorize(ColorSeek, FALSE);
3082 curColor = ColorSeek;
3087 if (looking_at(buf, &i, "\\ ")) {
3088 if (prevColor != ColorNormal) {
3089 if (oldi > next_out) {
3090 SendToPlayer(&buf[next_out], oldi - next_out);
3093 Colorize(prevColor, TRUE);
3094 curColor = prevColor;
3096 if (savingComment) {
3097 parse_pos = i - oldi;
3098 memcpy(parse, &buf[oldi], parse_pos);
3099 parse[parse_pos] = NULLCHAR;
3100 started = STARTED_COMMENT;
3101 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3102 chattingPartner = savingComment - 3; // kludge to remember the box
3104 started = STARTED_CHATTER;
3109 if (looking_at(buf, &i, "Black Strength :") ||
3110 looking_at(buf, &i, "<<< style 10 board >>>") ||
3111 looking_at(buf, &i, "<10>") ||
3112 looking_at(buf, &i, "#@#")) {
3113 /* Wrong board style */
3115 SendToICS(ics_prefix);
3116 SendToICS("set style 12\n");
3117 SendToICS(ics_prefix);
3118 SendToICS("refresh\n");
3122 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3124 have_sent_ICS_logon = 1;
3128 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3129 (looking_at(buf, &i, "\n<12> ") ||
3130 looking_at(buf, &i, "<12> "))) {
3132 if (oldi > next_out) {
3133 SendToPlayer(&buf[next_out], oldi - next_out);
3136 started = STARTED_BOARD;
3141 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3142 looking_at(buf, &i, "<b1> ")) {
3143 if (oldi > next_out) {
3144 SendToPlayer(&buf[next_out], oldi - next_out);
3147 started = STARTED_HOLDINGS;
3152 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3154 /* Header for a move list -- first line */
3156 switch (ics_getting_history) {
3160 case BeginningOfGame:
3161 /* User typed "moves" or "oldmoves" while we
3162 were idle. Pretend we asked for these
3163 moves and soak them up so user can step
3164 through them and/or save them.
3167 gameMode = IcsObserving;
3170 ics_getting_history = H_GOT_UNREQ_HEADER;
3172 case EditGame: /*?*/
3173 case EditPosition: /*?*/
3174 /* Should above feature work in these modes too? */
3175 /* For now it doesn't */
3176 ics_getting_history = H_GOT_UNWANTED_HEADER;
3179 ics_getting_history = H_GOT_UNWANTED_HEADER;
3184 /* Is this the right one? */
3185 if (gameInfo.white && gameInfo.black &&
3186 strcmp(gameInfo.white, star_match[0]) == 0 &&
3187 strcmp(gameInfo.black, star_match[2]) == 0) {
3189 ics_getting_history = H_GOT_REQ_HEADER;
3192 case H_GOT_REQ_HEADER:
3193 case H_GOT_UNREQ_HEADER:
3194 case H_GOT_UNWANTED_HEADER:
3195 case H_GETTING_MOVES:
3196 /* Should not happen */
3197 DisplayError(_("Error gathering move list: two headers"), 0);
3198 ics_getting_history = H_FALSE;
3202 /* Save player ratings into gameInfo if needed */
3203 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3204 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3205 (gameInfo.whiteRating == -1 ||
3206 gameInfo.blackRating == -1)) {
3208 gameInfo.whiteRating = string_to_rating(star_match[1]);
3209 gameInfo.blackRating = string_to_rating(star_match[3]);
3210 if (appData.debugMode)
3211 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3212 gameInfo.whiteRating, gameInfo.blackRating);
3217 if (looking_at(buf, &i,
3218 "* * match, initial time: * minute*, increment: * second")) {
3219 /* Header for a move list -- second line */
3220 /* Initial board will follow if this is a wild game */
3221 if (gameInfo.event != NULL) free(gameInfo.event);
3222 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3223 gameInfo.event = StrSave(str);
3224 /* [HGM] we switched variant. Translate boards if needed. */
3225 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3229 if (looking_at(buf, &i, "Move ")) {
3230 /* Beginning of a move list */
3231 switch (ics_getting_history) {
3233 /* Normally should not happen */
3234 /* Maybe user hit reset while we were parsing */
3237 /* Happens if we are ignoring a move list that is not
3238 * the one we just requested. Common if the user
3239 * tries to observe two games without turning off
3242 case H_GETTING_MOVES:
3243 /* Should not happen */
3244 DisplayError(_("Error gathering move list: nested"), 0);
3245 ics_getting_history = H_FALSE;
3247 case H_GOT_REQ_HEADER:
3248 ics_getting_history = H_GETTING_MOVES;
3249 started = STARTED_MOVES;
3251 if (oldi > next_out) {
3252 SendToPlayer(&buf[next_out], oldi - next_out);
3255 case H_GOT_UNREQ_HEADER:
3256 ics_getting_history = H_GETTING_MOVES;
3257 started = STARTED_MOVES_NOHIDE;
3260 case H_GOT_UNWANTED_HEADER:
3261 ics_getting_history = H_FALSE;
3267 if (looking_at(buf, &i, "% ") ||
3268 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3269 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3270 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3271 soughtPending = FALSE;
3275 if(suppressKibitz) next_out = i;
3276 savingComment = FALSE;
3280 case STARTED_MOVES_NOHIDE:
3281 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3282 parse[parse_pos + i - oldi] = NULLCHAR;
3283 ParseGameHistory(parse);
3285 if (appData.zippyPlay && first.initDone) {
3286 FeedMovesToProgram(&first, forwardMostMove);
3287 if (gameMode == IcsPlayingWhite) {
3288 if (WhiteOnMove(forwardMostMove)) {
3289 if (first.sendTime) {
3290 if (first.useColors) {
3291 SendToProgram("black\n", &first);
3293 SendTimeRemaining(&first, TRUE);
3295 if (first.useColors) {
3296 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3298 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3299 first.maybeThinking = TRUE;
3301 if (first.usePlayother) {
3302 if (first.sendTime) {
3303 SendTimeRemaining(&first, TRUE);
3305 SendToProgram("playother\n", &first);
3311 } else if (gameMode == IcsPlayingBlack) {
3312 if (!WhiteOnMove(forwardMostMove)) {
3313 if (first.sendTime) {
3314 if (first.useColors) {
3315 SendToProgram("white\n", &first);
3317 SendTimeRemaining(&first, FALSE);
3319 if (first.useColors) {
3320 SendToProgram("black\n", &first);
3322 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3323 first.maybeThinking = TRUE;
3325 if (first.usePlayother) {
3326 if (first.sendTime) {
3327 SendTimeRemaining(&first, FALSE);
3329 SendToProgram("playother\n", &first);
3338 if (gameMode == IcsObserving && ics_gamenum == -1) {
3339 /* Moves came from oldmoves or moves command
3340 while we weren't doing anything else.
3342 currentMove = forwardMostMove;
3343 ClearHighlights();/*!!could figure this out*/
3344 flipView = appData.flipView;
3345 DrawPosition(TRUE, boards[currentMove]);
3346 DisplayBothClocks();
3347 snprintf(str, MSG_SIZ, "%s vs. %s",
3348 gameInfo.white, gameInfo.black);
3352 /* Moves were history of an active game */
3353 if (gameInfo.resultDetails != NULL) {
3354 free(gameInfo.resultDetails);
3355 gameInfo.resultDetails = NULL;
3358 HistorySet(parseList, backwardMostMove,
3359 forwardMostMove, currentMove-1);
3360 DisplayMove(currentMove - 1);
3361 if (started == STARTED_MOVES) next_out = i;
3362 started = STARTED_NONE;
3363 ics_getting_history = H_FALSE;
3366 case STARTED_OBSERVE:
3367 started = STARTED_NONE;
3368 SendToICS(ics_prefix);
3369 SendToICS("refresh\n");
3375 if(bookHit) { // [HGM] book: simulate book reply
3376 static char bookMove[MSG_SIZ]; // a bit generous?
3378 programStats.nodes = programStats.depth = programStats.time =
3379 programStats.score = programStats.got_only_move = 0;
3380 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3382 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3383 strcat(bookMove, bookHit);
3384 HandleMachineMove(bookMove, &first);
3389 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3390 started == STARTED_HOLDINGS ||
3391 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3392 /* Accumulate characters in move list or board */
3393 parse[parse_pos++] = buf[i];
3396 /* Start of game messages. Mostly we detect start of game
3397 when the first board image arrives. On some versions
3398 of the ICS, though, we need to do a "refresh" after starting
3399 to observe in order to get the current board right away. */
3400 if (looking_at(buf, &i, "Adding game * to observation list")) {
3401 started = STARTED_OBSERVE;
3405 /* Handle auto-observe */
3406 if (appData.autoObserve &&
3407 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3408 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3410 /* Choose the player that was highlighted, if any. */
3411 if (star_match[0][0] == '\033' ||
3412 star_match[1][0] != '\033') {
3413 player = star_match[0];
3415 player = star_match[2];
3417 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3418 ics_prefix, StripHighlightAndTitle(player));
3421 /* Save ratings from notify string */
3422 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3423 player1Rating = string_to_rating(star_match[1]);
3424 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3425 player2Rating = string_to_rating(star_match[3]);
3427 if (appData.debugMode)
3429 "Ratings from 'Game notification:' %s %d, %s %d\n",
3430 player1Name, player1Rating,
3431 player2Name, player2Rating);
3436 /* Deal with automatic examine mode after a game,
3437 and with IcsObserving -> IcsExamining transition */
3438 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3439 looking_at(buf, &i, "has made you an examiner of game *")) {
3441 int gamenum = atoi(star_match[0]);
3442 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3443 gamenum == ics_gamenum) {
3444 /* We were already playing or observing this game;
3445 no need to refetch history */
3446 gameMode = IcsExamining;
3448 pauseExamForwardMostMove = forwardMostMove;
3449 } else if (currentMove < forwardMostMove) {
3450 ForwardInner(forwardMostMove);
3453 /* I don't think this case really can happen */
3454 SendToICS(ics_prefix);
3455 SendToICS("refresh\n");
3460 /* Error messages */
3461 // if (ics_user_moved) {
3462 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3463 if (looking_at(buf, &i, "Illegal move") ||
3464 looking_at(buf, &i, "Not a legal move") ||
3465 looking_at(buf, &i, "Your king is in check") ||
3466 looking_at(buf, &i, "It isn't your turn") ||
3467 looking_at(buf, &i, "It is not your move")) {
3469 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3470 currentMove = forwardMostMove-1;
3471 DisplayMove(currentMove - 1); /* before DMError */
3472 DrawPosition(FALSE, boards[currentMove]);
3473 SwitchClocks(forwardMostMove-1); // [HGM] race
3474 DisplayBothClocks();
3476 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3482 if (looking_at(buf, &i, "still have time") ||
3483 looking_at(buf, &i, "not out of time") ||
3484 looking_at(buf, &i, "either player is out of time") ||
3485 looking_at(buf, &i, "has timeseal; checking")) {
3486 /* We must have called his flag a little too soon */
3487 whiteFlag = blackFlag = FALSE;
3491 if (looking_at(buf, &i, "added * seconds to") ||
3492 looking_at(buf, &i, "seconds were added to")) {
3493 /* Update the clocks */
3494 SendToICS(ics_prefix);
3495 SendToICS("refresh\n");
3499 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3500 ics_clock_paused = TRUE;
3505 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3506 ics_clock_paused = FALSE;
3511 /* Grab player ratings from the Creating: message.
3512 Note we have to check for the special case when
3513 the ICS inserts things like [white] or [black]. */
3514 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3515 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3517 0 player 1 name (not necessarily white)
3519 2 empty, white, or black (IGNORED)
3520 3 player 2 name (not necessarily black)
3523 The names/ratings are sorted out when the game
3524 actually starts (below).
3526 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3527 player1Rating = string_to_rating(star_match[1]);
3528 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3529 player2Rating = string_to_rating(star_match[4]);
3531 if (appData.debugMode)
3533 "Ratings from 'Creating:' %s %d, %s %d\n",
3534 player1Name, player1Rating,
3535 player2Name, player2Rating);
3540 /* Improved generic start/end-of-game messages */
3541 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3542 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3543 /* If tkind == 0: */
3544 /* star_match[0] is the game number */
3545 /* [1] is the white player's name */
3546 /* [2] is the black player's name */
3547 /* For end-of-game: */
3548 /* [3] is the reason for the game end */
3549 /* [4] is a PGN end game-token, preceded by " " */
3550 /* For start-of-game: */
3551 /* [3] begins with "Creating" or "Continuing" */
3552 /* [4] is " *" or empty (don't care). */
3553 int gamenum = atoi(star_match[0]);
3554 char *whitename, *blackname, *why, *endtoken;
3555 ChessMove endtype = EndOfFile;
3558 whitename = star_match[1];
3559 blackname = star_match[2];
3560 why = star_match[3];
3561 endtoken = star_match[4];
3563 whitename = star_match[1];
3564 blackname = star_match[3];
3565 why = star_match[5];
3566 endtoken = star_match[6];
3569 /* Game start messages */
3570 if (strncmp(why, "Creating ", 9) == 0 ||
3571 strncmp(why, "Continuing ", 11) == 0) {
3572 gs_gamenum = gamenum;
3573 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3574 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3576 if (appData.zippyPlay) {
3577 ZippyGameStart(whitename, blackname);
3580 partnerBoardValid = FALSE; // [HGM] bughouse
3584 /* Game end messages */
3585 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3586 ics_gamenum != gamenum) {
3589 while (endtoken[0] == ' ') endtoken++;
3590 switch (endtoken[0]) {
3593 endtype = GameUnfinished;
3596 endtype = BlackWins;
3599 if (endtoken[1] == '/')
3600 endtype = GameIsDrawn;
3602 endtype = WhiteWins;
3605 GameEnds(endtype, why, GE_ICS);
3607 if (appData.zippyPlay && first.initDone) {
3608 ZippyGameEnd(endtype, why);
3609 if (first.pr == NULL) {
3610 /* Start the next process early so that we'll
3611 be ready for the next challenge */
3612 StartChessProgram(&first);
3614 /* Send "new" early, in case this command takes
3615 a long time to finish, so that we'll be ready
3616 for the next challenge. */
3617 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3621 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3625 if (looking_at(buf, &i, "Removing game * from observation") ||
3626 looking_at(buf, &i, "no longer observing game *") ||
3627 looking_at(buf, &i, "Game * (*) has no examiners")) {
3628 if (gameMode == IcsObserving &&
3629 atoi(star_match[0]) == ics_gamenum)
3631 /* icsEngineAnalyze */
3632 if (appData.icsEngineAnalyze) {
3639 ics_user_moved = FALSE;
3644 if (looking_at(buf, &i, "no longer examining game *")) {
3645 if (gameMode == IcsExamining &&
3646 atoi(star_match[0]) == ics_gamenum)
3650 ics_user_moved = FALSE;
3655 /* Advance leftover_start past any newlines we find,
3656 so only partial lines can get reparsed */
3657 if (looking_at(buf, &i, "\n")) {
3658 prevColor = curColor;
3659 if (curColor != ColorNormal) {
3660 if (oldi > next_out) {
3661 SendToPlayer(&buf[next_out], oldi - next_out);
3664 Colorize(ColorNormal, FALSE);
3665 curColor = ColorNormal;
3667 if (started == STARTED_BOARD) {
3668 started = STARTED_NONE;
3669 parse[parse_pos] = NULLCHAR;
3670 ParseBoard12(parse);
3673 /* Send premove here */
3674 if (appData.premove) {
3676 if (currentMove == 0 &&
3677 gameMode == IcsPlayingWhite &&
3678 appData.premoveWhite) {
3679 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3680 if (appData.debugMode)
3681 fprintf(debugFP, "Sending premove:\n");
3683 } else if (currentMove == 1 &&
3684 gameMode == IcsPlayingBlack &&
3685 appData.premoveBlack) {
3686 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3687 if (appData.debugMode)
3688 fprintf(debugFP, "Sending premove:\n");
3690 } else if (gotPremove) {
3692 ClearPremoveHighlights();
3693 if (appData.debugMode)
3694 fprintf(debugFP, "Sending premove:\n");
3695 UserMoveEvent(premoveFromX, premoveFromY,
3696 premoveToX, premoveToY,
3701 /* Usually suppress following prompt */
3702 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3703 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3704 if (looking_at(buf, &i, "*% ")) {
3705 savingComment = FALSE;
3710 } else if (started == STARTED_HOLDINGS) {
3712 char new_piece[MSG_SIZ];
3713 started = STARTED_NONE;
3714 parse[parse_pos] = NULLCHAR;
3715 if (appData.debugMode)
3716 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3717 parse, currentMove);
3718 if (sscanf(parse, " game %d", &gamenum) == 1) {
3719 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3720 if (gameInfo.variant == VariantNormal) {
3721 /* [HGM] We seem to switch variant during a game!
3722 * Presumably no holdings were displayed, so we have
3723 * to move the position two files to the right to
3724 * create room for them!
3726 VariantClass newVariant;
3727 switch(gameInfo.boardWidth) { // base guess on board width
3728 case 9: newVariant = VariantShogi; break;
3729 case 10: newVariant = VariantGreat; break;
3730 default: newVariant = VariantCrazyhouse; break;
3732 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3733 /* Get a move list just to see the header, which
3734 will tell us whether this is really bug or zh */
3735 if (ics_getting_history == H_FALSE) {
3736 ics_getting_history = H_REQUESTED;
3737 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3741 new_piece[0] = NULLCHAR;
3742 sscanf(parse, "game %d white [%s black [%s <- %s",
3743 &gamenum, white_holding, black_holding,
3745 white_holding[strlen(white_holding)-1] = NULLCHAR;
3746 black_holding[strlen(black_holding)-1] = NULLCHAR;
3747 /* [HGM] copy holdings to board holdings area */
3748 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3749 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3750 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3752 if (appData.zippyPlay && first.initDone) {
3753 ZippyHoldings(white_holding, black_holding,
3757 if (tinyLayout || smallLayout) {
3758 char wh[16], bh[16];
3759 PackHolding(wh, white_holding);
3760 PackHolding(bh, black_holding);
3761 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3762 gameInfo.white, gameInfo.black);
3764 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3765 gameInfo.white, white_holding,
3766 gameInfo.black, black_holding);
3768 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3769 DrawPosition(FALSE, boards[currentMove]);
3771 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3772 sscanf(parse, "game %d white [%s black [%s <- %s",
3773 &gamenum, white_holding, black_holding,
3775 white_holding[strlen(white_holding)-1] = NULLCHAR;
3776 black_holding[strlen(black_holding)-1] = NULLCHAR;
3777 /* [HGM] copy holdings to partner-board holdings area */
3778 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3779 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3780 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3781 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3782 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3785 /* Suppress following prompt */
3786 if (looking_at(buf, &i, "*% ")) {
3787 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3788 savingComment = FALSE;