2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy( char *dst, const char *src, size_t count )
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble(u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
398 [AS] Note: sometimes, the sscanf() function is used to parse the input
399 into a fixed-size buffer. Because of this, we must be prepared to
400 receive strings as long as the size of the input buffer, which is currently
401 set to 4K for Windows and 8K for the rest.
402 So, we must either allocate sufficiently large buffers here, or
403 reduce the size of the input buffer in the input reading part.
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
410 ChessProgramState first, second;
412 /* premove variables */
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
465 /* animateTraining preserves the state of appData.animate
466 * when Training mode is activated. This allows the
467 * response to be animated when appData.animate == TRUE and
468 * appData.animateDragging == TRUE.
470 Boolean animateTraining;
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char initialRights[BOARD_FILES];
480 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int initialRulePlies, FENrulePlies;
482 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int mute; // mute all sounds
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
501 ChessSquare FIDEArray[2][BOARD_FILES] = {
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505 BlackKing, BlackBishop, BlackKnight, BlackRook }
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackKing, BlackKnight, BlackRook }
515 ChessSquare KnightmateArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518 { BlackRook, BlackMan, BlackBishop, BlackQueen,
519 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackMan, BlackFerz,
547 BlackKing, BlackMan, BlackKnight, BlackRook }
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
595 #define GothicArray CapablancaArray
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
606 #define FalconArray CapablancaArray
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
628 Board initialPosition;
631 /* Convert str to a rating. Checks for special cases of "----",
633 "++++", etc. Also strips ()'s */
635 string_to_rating(str)
638 while(*str && !isdigit(*str)) ++str;
640 return 0; /* One of the special "no rating" cases */
648 /* Init programStats */
649 programStats.movelist[0] = 0;
650 programStats.depth = 0;
651 programStats.nr_moves = 0;
652 programStats.moves_left = 0;
653 programStats.nodes = 0;
654 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
655 programStats.score = 0;
656 programStats.got_only_move = 0;
657 programStats.got_fail = 0;
658 programStats.line_is_book = 0;
663 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
664 if (appData.firstPlaysBlack) {
665 first.twoMachinesColor = "black\n";
666 second.twoMachinesColor = "white\n";
668 first.twoMachinesColor = "white\n";
669 second.twoMachinesColor = "black\n";
672 first.other = &second;
673 second.other = &first;
676 if(appData.timeOddsMode) {
677 norm = appData.timeOdds[0];
678 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
680 first.timeOdds = appData.timeOdds[0]/norm;
681 second.timeOdds = appData.timeOdds[1]/norm;
684 if(programVersion) free(programVersion);
685 if (appData.noChessProgram) {
686 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
687 sprintf(programVersion, "%s", PACKAGE_STRING);
689 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
690 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
691 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
696 UnloadEngine(ChessProgramState *cps)
698 /* Kill off first chess program */
699 if (cps->isr != NULL)
700 RemoveInputSource(cps->isr);
703 if (cps->pr != NoProc) {
705 DoSleep( appData.delayBeforeQuit );
706 SendToProgram("quit\n", cps);
707 DoSleep( appData.delayAfterQuit );
708 DestroyChildProcess(cps->pr, cps->useSigterm);
714 ClearOptions(ChessProgramState *cps)
717 cps->nrOptions = cps->comboCnt = 0;
718 for(i=0; i<MAX_OPTIONS; i++) {
719 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
720 cps->option[i].textValue = 0;
724 char *engineNames[] = {
729 InitEngine(ChessProgramState *cps, int n)
730 { // [HGM] all engine initialiation put in a function that does one engine
734 cps->which = engineNames[n];
735 cps->maybeThinking = FALSE;
739 cps->sendDrawOffers = 1;
741 cps->program = appData.chessProgram[n];
742 cps->host = appData.host[n];
743 cps->dir = appData.directory[n];
744 cps->initString = appData.engInitString[n];
745 cps->computerString = appData.computerString[n];
746 cps->useSigint = TRUE;
747 cps->useSigterm = TRUE;
748 cps->reuse = appData.reuse[n];
749 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
750 cps->useSetboard = FALSE;
752 cps->usePing = FALSE;
755 cps->usePlayother = FALSE;
756 cps->useColors = TRUE;
757 cps->useUsermove = FALSE;
758 cps->sendICS = FALSE;
759 cps->sendName = appData.icsActive;
760 cps->sdKludge = FALSE;
761 cps->stKludge = FALSE;
762 TidyProgramName(cps->program, cps->host, cps->tidy);
764 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
765 cps->analysisSupport = 2; /* detect */
766 cps->analyzing = FALSE;
767 cps->initDone = FALSE;
769 /* New features added by Tord: */
770 cps->useFEN960 = FALSE;
771 cps->useOOCastle = TRUE;
772 /* End of new features added by Tord. */
773 cps->fenOverride = appData.fenOverride[n];
775 /* [HGM] time odds: set factor for each machine */
776 cps->timeOdds = appData.timeOdds[n];
778 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
779 cps->accumulateTC = appData.accumulateTC[n];
780 cps->maxNrOfSessions = 1;
784 cps->supportsNPS = UNKNOWN;
787 cps->optionSettings = appData.engOptions[n];
789 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
790 cps->isUCI = appData.isUCI[n]; /* [AS] */
791 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
793 if (appData.protocolVersion[n] > PROTOVER
794 || appData.protocolVersion[n] < 1)
799 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
800 appData.protocolVersion[n]);
801 if( (len > MSG_SIZ) && appData.debugMode )
802 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
804 DisplayFatalError(buf, 0, 2);
808 cps->protocolVersion = appData.protocolVersion[n];
811 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
817 int matched, min, sec;
819 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
820 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
822 GetTimeMark(&programStartTime);
823 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
826 programStats.ok_to_send = 1;
827 programStats.seen_stat = 0;
830 * Initialize game list
836 * Internet chess server status
838 if (appData.icsActive) {
839 appData.matchMode = FALSE;
840 appData.matchGames = 0;
842 appData.noChessProgram = !appData.zippyPlay;
844 appData.zippyPlay = FALSE;
845 appData.zippyTalk = FALSE;
846 appData.noChessProgram = TRUE;
848 if (*appData.icsHelper != NULLCHAR) {
849 appData.useTelnet = TRUE;
850 appData.telnetProgram = appData.icsHelper;
853 appData.zippyTalk = appData.zippyPlay = FALSE;
856 /* [AS] Initialize pv info list [HGM] and game state */
860 for( i=0; i<=framePtr; i++ ) {
861 pvInfoList[i].depth = -1;
862 boards[i][EP_STATUS] = EP_NONE;
863 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
868 * Parse timeControl resource
870 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
871 appData.movesPerSession)) {
873 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
874 DisplayFatalError(buf, 0, 2);
878 * Parse searchTime resource
880 if (*appData.searchTime != NULLCHAR) {
881 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
883 searchTime = min * 60;
884 } else if (matched == 2) {
885 searchTime = min * 60 + sec;
888 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
889 DisplayFatalError(buf, 0, 2);
893 /* [AS] Adjudication threshold */
894 adjudicateLossThreshold = appData.adjudicateLossThreshold;
896 InitEngine(&first, 0);
897 InitEngine(&second, 1);
900 if (appData.icsActive) {
901 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
902 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
903 appData.clockMode = FALSE;
904 first.sendTime = second.sendTime = 0;
908 /* Override some settings from environment variables, for backward
909 compatibility. Unfortunately it's not feasible to have the env
910 vars just set defaults, at least in xboard. Ugh.
912 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
917 if (!appData.icsActive) {
921 /* Check for variants that are supported only in ICS mode,
922 or not at all. Some that are accepted here nevertheless
923 have bugs; see comments below.
925 VariantClass variant = StringToVariant(appData.variant);
927 case VariantBughouse: /* need four players and two boards */
928 case VariantKriegspiel: /* need to hide pieces and move details */
929 /* case VariantFischeRandom: (Fabien: moved below) */
930 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
931 if( (len > MSG_SIZ) && appData.debugMode )
932 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
934 DisplayFatalError(buf, 0, 2);
938 case VariantLoadable:
948 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
949 if( (len > MSG_SIZ) && appData.debugMode )
950 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
952 DisplayFatalError(buf, 0, 2);
955 case VariantXiangqi: /* [HGM] repetition rules not implemented */
956 case VariantFairy: /* [HGM] TestLegality definitely off! */
957 case VariantGothic: /* [HGM] should work */
958 case VariantCapablanca: /* [HGM] should work */
959 case VariantCourier: /* [HGM] initial forced moves not implemented */
960 case VariantShogi: /* [HGM] could still mate with pawn drop */
961 case VariantKnightmate: /* [HGM] should work */
962 case VariantCylinder: /* [HGM] untested */
963 case VariantFalcon: /* [HGM] untested */
964 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
965 offboard interposition not understood */
966 case VariantNormal: /* definitely works! */
967 case VariantWildCastle: /* pieces not automatically shuffled */
968 case VariantNoCastle: /* pieces not automatically shuffled */
969 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
970 case VariantLosers: /* should work except for win condition,
971 and doesn't know captures are mandatory */
972 case VariantSuicide: /* should work except for win condition,
973 and doesn't know captures are mandatory */
974 case VariantGiveaway: /* should work except for win condition,
975 and doesn't know captures are mandatory */
976 case VariantTwoKings: /* should work */
977 case VariantAtomic: /* should work except for win condition */
978 case Variant3Check: /* should work except for win condition */
979 case VariantShatranj: /* should work except for all win conditions */
980 case VariantMakruk: /* should work except for daw countdown */
981 case VariantBerolina: /* might work if TestLegality is off */
982 case VariantCapaRandom: /* should work */
983 case VariantJanus: /* should work */
984 case VariantSuper: /* experimental */
985 case VariantGreat: /* experimental, requires legality testing to be off */
986 case VariantSChess: /* S-Chess, should work */
987 case VariantSpartan: /* should work */
994 int NextIntegerFromString( char ** str, long * value )
999 while( *s == ' ' || *s == '\t' ) {
1005 if( *s >= '0' && *s <= '9' ) {
1006 while( *s >= '0' && *s <= '9' ) {
1007 *value = *value * 10 + (*s - '0');
1019 int NextTimeControlFromString( char ** str, long * value )
1022 int result = NextIntegerFromString( str, &temp );
1025 *value = temp * 60; /* Minutes */
1026 if( **str == ':' ) {
1028 result = NextIntegerFromString( str, &temp );
1029 *value += temp; /* Seconds */
1036 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1037 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1038 int result = -1, type = 0; long temp, temp2;
1040 if(**str != ':') return -1; // old params remain in force!
1042 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1043 if( NextIntegerFromString( str, &temp ) ) return -1;
1044 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1047 /* time only: incremental or sudden-death time control */
1048 if(**str == '+') { /* increment follows; read it */
1050 if(**str == '!') type = *(*str)++; // Bronstein TC
1051 if(result = NextIntegerFromString( str, &temp2)) return -1;
1052 *inc = temp2 * 1000;
1053 if(**str == '.') { // read fraction of increment
1054 char *start = ++(*str);
1055 if(result = NextIntegerFromString( str, &temp2)) return -1;
1057 while(start++ < *str) temp2 /= 10;
1061 *moves = 0; *tc = temp * 1000; *incType = type;
1065 (*str)++; /* classical time control */
1066 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1077 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1078 { /* [HGM] get time to add from the multi-session time-control string */
1079 int incType, moves=1; /* kludge to force reading of first session */
1080 long time, increment;
1083 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1084 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1086 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1087 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1088 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1089 if(movenr == -1) return time; /* last move before new session */
1090 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1091 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1092 if(!moves) return increment; /* current session is incremental */
1093 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1094 } while(movenr >= -1); /* try again for next session */
1096 return 0; // no new time quota on this move
1100 ParseTimeControl(tc, ti, mps)
1107 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1110 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1111 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1112 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1116 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1118 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1121 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1123 snprintf(buf, MSG_SIZ, ":%s", mytc);
1125 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1127 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1132 /* Parse second time control */
1135 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1143 timeControl_2 = tc2 * 1000;
1153 timeControl = tc1 * 1000;
1156 timeIncrement = ti * 1000; /* convert to ms */
1157 movesPerSession = 0;
1160 movesPerSession = mps;
1168 if (appData.debugMode) {
1169 fprintf(debugFP, "%s\n", programVersion);
1172 set_cont_sequence(appData.wrapContSeq);
1173 if (appData.matchGames > 0) {
1174 appData.matchMode = TRUE;
1175 } else if (appData.matchMode) {
1176 appData.matchGames = 1;
1178 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1179 appData.matchGames = appData.sameColorGames;
1180 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1181 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1182 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1185 if (appData.noChessProgram || first.protocolVersion == 1) {
1188 /* kludge: allow timeout for initial "feature" commands */
1190 DisplayMessage("", _("Starting chess program"));
1191 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1196 MatchEvent(int mode)
1197 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1198 /* Set up machine vs. machine match */
1199 if (appData.noChessProgram) {
1200 DisplayFatalError(_("Can't have a match with no chess programs"),
1206 if (*appData.loadGameFile != NULLCHAR) {
1207 int index = appData.loadGameIndex; // [HGM] autoinc
1208 if(index<0) lastIndex = index = 1;
1209 if (!LoadGameFromFile(appData.loadGameFile,
1211 appData.loadGameFile, FALSE)) {
1212 DisplayFatalError(_("Bad game file"), 0, 1);
1215 } else if (*appData.loadPositionFile != NULLCHAR) {
1216 int index = appData.loadPositionIndex; // [HGM] autoinc
1217 if(index<0) lastIndex = index = 1;
1218 if (!LoadPositionFromFile(appData.loadPositionFile,
1220 appData.loadPositionFile)) {
1221 DisplayFatalError(_("Bad position file"), 0, 1);
1225 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1230 InitBackEnd3 P((void))
1232 GameMode initialMode;
1236 InitChessProgram(&first, startedFromSetupPosition);
1238 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1239 free(programVersion);
1240 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1241 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1244 if (appData.icsActive) {
1246 /* [DM] Make a console window if needed [HGM] merged ifs */
1252 if (*appData.icsCommPort != NULLCHAR)
1253 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1254 appData.icsCommPort);
1256 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1257 appData.icsHost, appData.icsPort);
1259 if( (len > MSG_SIZ) && appData.debugMode )
1260 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1262 DisplayFatalError(buf, err, 1);
1267 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1269 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1270 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1271 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1272 } else if (appData.noChessProgram) {
1278 if (*appData.cmailGameName != NULLCHAR) {
1280 OpenLoopback(&cmailPR);
1282 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1286 DisplayMessage("", "");
1287 if (StrCaseCmp(appData.initialMode, "") == 0) {
1288 initialMode = BeginningOfGame;
1289 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1290 initialMode = TwoMachinesPlay;
1291 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1292 initialMode = AnalyzeFile;
1293 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1294 initialMode = AnalyzeMode;
1295 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1296 initialMode = MachinePlaysWhite;
1297 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1298 initialMode = MachinePlaysBlack;
1299 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1300 initialMode = EditGame;
1301 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1302 initialMode = EditPosition;
1303 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1304 initialMode = Training;
1306 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1307 if( (len > MSG_SIZ) && appData.debugMode )
1308 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1310 DisplayFatalError(buf, 0, 2);
1314 if (appData.matchMode) {
1316 } else if (*appData.cmailGameName != NULLCHAR) {
1317 /* Set up cmail mode */
1318 ReloadCmailMsgEvent(TRUE);
1320 /* Set up other modes */
1321 if (initialMode == AnalyzeFile) {
1322 if (*appData.loadGameFile == NULLCHAR) {
1323 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1327 if (*appData.loadGameFile != NULLCHAR) {
1328 (void) LoadGameFromFile(appData.loadGameFile,
1329 appData.loadGameIndex,
1330 appData.loadGameFile, TRUE);
1331 } else if (*appData.loadPositionFile != NULLCHAR) {
1332 (void) LoadPositionFromFile(appData.loadPositionFile,
1333 appData.loadPositionIndex,
1334 appData.loadPositionFile);
1335 /* [HGM] try to make self-starting even after FEN load */
1336 /* to allow automatic setup of fairy variants with wtm */
1337 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1338 gameMode = BeginningOfGame;
1339 setboardSpoiledMachineBlack = 1;
1341 /* [HGM] loadPos: make that every new game uses the setup */
1342 /* from file as long as we do not switch variant */
1343 if(!blackPlaysFirst) {
1344 startedFromPositionFile = TRUE;
1345 CopyBoard(filePosition, boards[0]);
1348 if (initialMode == AnalyzeMode) {
1349 if (appData.noChessProgram) {
1350 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1353 if (appData.icsActive) {
1354 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1358 } else if (initialMode == AnalyzeFile) {
1359 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1360 ShowThinkingEvent();
1362 AnalysisPeriodicEvent(1);
1363 } else if (initialMode == MachinePlaysWhite) {
1364 if (appData.noChessProgram) {
1365 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1369 if (appData.icsActive) {
1370 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1374 MachineWhiteEvent();
1375 } else if (initialMode == MachinePlaysBlack) {
1376 if (appData.noChessProgram) {
1377 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1381 if (appData.icsActive) {
1382 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1386 MachineBlackEvent();
1387 } else if (initialMode == TwoMachinesPlay) {
1388 if (appData.noChessProgram) {
1389 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1393 if (appData.icsActive) {
1394 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1399 } else if (initialMode == EditGame) {
1401 } else if (initialMode == EditPosition) {
1402 EditPositionEvent();
1403 } else if (initialMode == Training) {
1404 if (*appData.loadGameFile == NULLCHAR) {
1405 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1414 * Establish will establish a contact to a remote host.port.
1415 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1416 * used to talk to the host.
1417 * Returns 0 if okay, error code if not.
1424 if (*appData.icsCommPort != NULLCHAR) {
1425 /* Talk to the host through a serial comm port */
1426 return OpenCommPort(appData.icsCommPort, &icsPR);
1428 } else if (*appData.gateway != NULLCHAR) {
1429 if (*appData.remoteShell == NULLCHAR) {
1430 /* Use the rcmd protocol to run telnet program on a gateway host */
1431 snprintf(buf, sizeof(buf), "%s %s %s",
1432 appData.telnetProgram, appData.icsHost, appData.icsPort);
1433 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1436 /* Use the rsh program to run telnet program on a gateway host */
1437 if (*appData.remoteUser == NULLCHAR) {
1438 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1439 appData.gateway, appData.telnetProgram,
1440 appData.icsHost, appData.icsPort);
1442 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1443 appData.remoteShell, appData.gateway,
1444 appData.remoteUser, appData.telnetProgram,
1445 appData.icsHost, appData.icsPort);
1447 return StartChildProcess(buf, "", &icsPR);
1450 } else if (appData.useTelnet) {
1451 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1454 /* TCP socket interface differs somewhat between
1455 Unix and NT; handle details in the front end.
1457 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1461 void EscapeExpand(char *p, char *q)
1462 { // [HGM] initstring: routine to shape up string arguments
1463 while(*p++ = *q++) if(p[-1] == '\\')
1465 case 'n': p[-1] = '\n'; break;
1466 case 'r': p[-1] = '\r'; break;
1467 case 't': p[-1] = '\t'; break;
1468 case '\\': p[-1] = '\\'; break;
1469 case 0: *p = 0; return;
1470 default: p[-1] = q[-1]; break;
1475 show_bytes(fp, buf, count)
1481 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1482 fprintf(fp, "\\%03o", *buf & 0xff);
1491 /* Returns an errno value */
1493 OutputMaybeTelnet(pr, message, count, outError)
1499 char buf[8192], *p, *q, *buflim;
1500 int left, newcount, outcount;
1502 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1503 *appData.gateway != NULLCHAR) {
1504 if (appData.debugMode) {
1505 fprintf(debugFP, ">ICS: ");
1506 show_bytes(debugFP, message, count);
1507 fprintf(debugFP, "\n");
1509 return OutputToProcess(pr, message, count, outError);
1512 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1519 if (appData.debugMode) {
1520 fprintf(debugFP, ">ICS: ");
1521 show_bytes(debugFP, buf, newcount);
1522 fprintf(debugFP, "\n");
1524 outcount = OutputToProcess(pr, buf, newcount, outError);
1525 if (outcount < newcount) return -1; /* to be sure */
1532 } else if (((unsigned char) *p) == TN_IAC) {
1533 *q++ = (char) TN_IAC;
1540 if (appData.debugMode) {
1541 fprintf(debugFP, ">ICS: ");
1542 show_bytes(debugFP, buf, newcount);
1543 fprintf(debugFP, "\n");
1545 outcount = OutputToProcess(pr, buf, newcount, outError);
1546 if (outcount < newcount) return -1; /* to be sure */
1551 read_from_player(isr, closure, message, count, error)
1558 int outError, outCount;
1559 static int gotEof = 0;
1561 /* Pass data read from player on to ICS */
1564 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1565 if (outCount < count) {
1566 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1568 } else if (count < 0) {
1569 RemoveInputSource(isr);
1570 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1571 } else if (gotEof++ > 0) {
1572 RemoveInputSource(isr);
1573 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1579 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1580 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1581 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1582 SendToICS("date\n");
1583 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1586 /* added routine for printf style output to ics */
1587 void ics_printf(char *format, ...)
1589 char buffer[MSG_SIZ];
1592 va_start(args, format);
1593 vsnprintf(buffer, sizeof(buffer), format, args);
1594 buffer[sizeof(buffer)-1] = '\0';
1603 int count, outCount, outError;
1605 if (icsPR == NULL) return;
1608 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1609 if (outCount < count) {
1610 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1614 /* This is used for sending logon scripts to the ICS. Sending
1615 without a delay causes problems when using timestamp on ICC
1616 (at least on my machine). */
1618 SendToICSDelayed(s,msdelay)
1622 int count, outCount, outError;
1624 if (icsPR == NULL) return;
1627 if (appData.debugMode) {
1628 fprintf(debugFP, ">ICS: ");
1629 show_bytes(debugFP, s, count);
1630 fprintf(debugFP, "\n");
1632 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1634 if (outCount < count) {
1635 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1640 /* Remove all highlighting escape sequences in s
1641 Also deletes any suffix starting with '('
1644 StripHighlightAndTitle(s)
1647 static char retbuf[MSG_SIZ];
1650 while (*s != NULLCHAR) {
1651 while (*s == '\033') {
1652 while (*s != NULLCHAR && !isalpha(*s)) s++;
1653 if (*s != NULLCHAR) s++;
1655 while (*s != NULLCHAR && *s != '\033') {
1656 if (*s == '(' || *s == '[') {
1667 /* Remove all highlighting escape sequences in s */
1672 static char retbuf[MSG_SIZ];
1675 while (*s != NULLCHAR) {
1676 while (*s == '\033') {
1677 while (*s != NULLCHAR && !isalpha(*s)) s++;
1678 if (*s != NULLCHAR) s++;
1680 while (*s != NULLCHAR && *s != '\033') {
1688 char *variantNames[] = VARIANT_NAMES;
1693 return variantNames[v];
1697 /* Identify a variant from the strings the chess servers use or the
1698 PGN Variant tag names we use. */
1705 VariantClass v = VariantNormal;
1706 int i, found = FALSE;
1712 /* [HGM] skip over optional board-size prefixes */
1713 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1714 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1715 while( *e++ != '_');
1718 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1722 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1723 if (StrCaseStr(e, variantNames[i])) {
1724 v = (VariantClass) i;
1731 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1732 || StrCaseStr(e, "wild/fr")
1733 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1734 v = VariantFischeRandom;
1735 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1736 (i = 1, p = StrCaseStr(e, "w"))) {
1738 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1745 case 0: /* FICS only, actually */
1747 /* Castling legal even if K starts on d-file */
1748 v = VariantWildCastle;
1753 /* Castling illegal even if K & R happen to start in
1754 normal positions. */
1755 v = VariantNoCastle;
1768 /* Castling legal iff K & R start in normal positions */
1774 /* Special wilds for position setup; unclear what to do here */
1775 v = VariantLoadable;
1778 /* Bizarre ICC game */
1779 v = VariantTwoKings;
1782 v = VariantKriegspiel;
1788 v = VariantFischeRandom;
1791 v = VariantCrazyhouse;
1794 v = VariantBughouse;
1800 /* Not quite the same as FICS suicide! */
1801 v = VariantGiveaway;
1807 v = VariantShatranj;
1810 /* Temporary names for future ICC types. The name *will* change in
1811 the next xboard/WinBoard release after ICC defines it. */
1849 v = VariantCapablanca;
1852 v = VariantKnightmate;
1858 v = VariantCylinder;
1864 v = VariantCapaRandom;
1867 v = VariantBerolina;
1879 /* Found "wild" or "w" in the string but no number;
1880 must assume it's normal chess. */
1884 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1885 if( (len > MSG_SIZ) && appData.debugMode )
1886 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1888 DisplayError(buf, 0);
1894 if (appData.debugMode) {
1895 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1896 e, wnum, VariantName(v));
1901 static int leftover_start = 0, leftover_len = 0;
1902 char star_match[STAR_MATCH_N][MSG_SIZ];
1904 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1905 advance *index beyond it, and set leftover_start to the new value of
1906 *index; else return FALSE. If pattern contains the character '*', it
1907 matches any sequence of characters not containing '\r', '\n', or the
1908 character following the '*' (if any), and the matched sequence(s) are
1909 copied into star_match.
1912 looking_at(buf, index, pattern)
1917 char *bufp = &buf[*index], *patternp = pattern;
1919 char *matchp = star_match[0];
1922 if (*patternp == NULLCHAR) {
1923 *index = leftover_start = bufp - buf;
1927 if (*bufp == NULLCHAR) return FALSE;
1928 if (*patternp == '*') {
1929 if (*bufp == *(patternp + 1)) {
1931 matchp = star_match[++star_count];
1935 } else if (*bufp == '\n' || *bufp == '\r') {
1937 if (*patternp == NULLCHAR)
1942 *matchp++ = *bufp++;
1946 if (*patternp != *bufp) return FALSE;
1953 SendToPlayer(data, length)
1957 int error, outCount;
1958 outCount = OutputToProcess(NoProc, data, length, &error);
1959 if (outCount < length) {
1960 DisplayFatalError(_("Error writing to display"), error, 1);
1965 PackHolding(packed, holding)
1977 switch (runlength) {
1988 sprintf(q, "%d", runlength);
2000 /* Telnet protocol requests from the front end */
2002 TelnetRequest(ddww, option)
2003 unsigned char ddww, option;
2005 unsigned char msg[3];
2006 int outCount, outError;
2008 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2010 if (appData.debugMode) {
2011 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2027 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2036 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2039 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2044 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2046 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2053 if (!appData.icsActive) return;
2054 TelnetRequest(TN_DO, TN_ECHO);
2060 if (!appData.icsActive) return;
2061 TelnetRequest(TN_DONT, TN_ECHO);
2065 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2067 /* put the holdings sent to us by the server on the board holdings area */
2068 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2072 if(gameInfo.holdingsWidth < 2) return;
2073 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2074 return; // prevent overwriting by pre-board holdings
2076 if( (int)lowestPiece >= BlackPawn ) {
2079 holdingsStartRow = BOARD_HEIGHT-1;
2082 holdingsColumn = BOARD_WIDTH-1;
2083 countsColumn = BOARD_WIDTH-2;
2084 holdingsStartRow = 0;
2088 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2089 board[i][holdingsColumn] = EmptySquare;
2090 board[i][countsColumn] = (ChessSquare) 0;
2092 while( (p=*holdings++) != NULLCHAR ) {
2093 piece = CharToPiece( ToUpper(p) );
2094 if(piece == EmptySquare) continue;
2095 /*j = (int) piece - (int) WhitePawn;*/
2096 j = PieceToNumber(piece);
2097 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2098 if(j < 0) continue; /* should not happen */
2099 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2100 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2101 board[holdingsStartRow+j*direction][countsColumn]++;
2107 VariantSwitch(Board board, VariantClass newVariant)
2109 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2110 static Board oldBoard;
2112 startedFromPositionFile = FALSE;
2113 if(gameInfo.variant == newVariant) return;
2115 /* [HGM] This routine is called each time an assignment is made to
2116 * gameInfo.variant during a game, to make sure the board sizes
2117 * are set to match the new variant. If that means adding or deleting
2118 * holdings, we shift the playing board accordingly
2119 * This kludge is needed because in ICS observe mode, we get boards
2120 * of an ongoing game without knowing the variant, and learn about the
2121 * latter only later. This can be because of the move list we requested,
2122 * in which case the game history is refilled from the beginning anyway,
2123 * but also when receiving holdings of a crazyhouse game. In the latter
2124 * case we want to add those holdings to the already received position.
2128 if (appData.debugMode) {
2129 fprintf(debugFP, "Switch board from %s to %s\n",
2130 VariantName(gameInfo.variant), VariantName(newVariant));
2131 setbuf(debugFP, NULL);
2133 shuffleOpenings = 0; /* [HGM] shuffle */
2134 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2138 newWidth = 9; newHeight = 9;
2139 gameInfo.holdingsSize = 7;
2140 case VariantBughouse:
2141 case VariantCrazyhouse:
2142 newHoldingsWidth = 2; break;
2146 newHoldingsWidth = 2;
2147 gameInfo.holdingsSize = 8;
2150 case VariantCapablanca:
2151 case VariantCapaRandom:
2154 newHoldingsWidth = gameInfo.holdingsSize = 0;
2157 if(newWidth != gameInfo.boardWidth ||
2158 newHeight != gameInfo.boardHeight ||
2159 newHoldingsWidth != gameInfo.holdingsWidth ) {
2161 /* shift position to new playing area, if needed */
2162 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2163 for(i=0; i<BOARD_HEIGHT; i++)
2164 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2165 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2167 for(i=0; i<newHeight; i++) {
2168 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2169 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2171 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2172 for(i=0; i<BOARD_HEIGHT; i++)
2173 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2174 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2177 gameInfo.boardWidth = newWidth;
2178 gameInfo.boardHeight = newHeight;
2179 gameInfo.holdingsWidth = newHoldingsWidth;
2180 gameInfo.variant = newVariant;
2181 InitDrawingSizes(-2, 0);
2182 } else gameInfo.variant = newVariant;
2183 CopyBoard(oldBoard, board); // remember correctly formatted board
2184 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2185 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2188 static int loggedOn = FALSE;
2190 /*-- Game start info cache: --*/
2192 char gs_kind[MSG_SIZ];
2193 static char player1Name[128] = "";
2194 static char player2Name[128] = "";
2195 static char cont_seq[] = "\n\\ ";
2196 static int player1Rating = -1;
2197 static int player2Rating = -1;
2198 /*----------------------------*/
2200 ColorClass curColor = ColorNormal;
2201 int suppressKibitz = 0;
2204 Boolean soughtPending = FALSE;
2205 Boolean seekGraphUp;
2206 #define MAX_SEEK_ADS 200
2208 char *seekAdList[MAX_SEEK_ADS];
2209 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2210 float tcList[MAX_SEEK_ADS];
2211 char colorList[MAX_SEEK_ADS];
2212 int nrOfSeekAds = 0;
2213 int minRating = 1010, maxRating = 2800;
2214 int hMargin = 10, vMargin = 20, h, w;
2215 extern int squareSize, lineGap;
2220 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2221 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2222 if(r < minRating+100 && r >=0 ) r = minRating+100;
2223 if(r > maxRating) r = maxRating;
2224 if(tc < 1.) tc = 1.;
2225 if(tc > 95.) tc = 95.;
2226 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2227 y = ((double)r - minRating)/(maxRating - minRating)
2228 * (h-vMargin-squareSize/8-1) + vMargin;
2229 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2230 if(strstr(seekAdList[i], " u ")) color = 1;
2231 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2232 !strstr(seekAdList[i], "bullet") &&
2233 !strstr(seekAdList[i], "blitz") &&
2234 !strstr(seekAdList[i], "standard") ) color = 2;
2235 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2236 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2240 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2242 char buf[MSG_SIZ], *ext = "";
2243 VariantClass v = StringToVariant(type);
2244 if(strstr(type, "wild")) {
2245 ext = type + 4; // append wild number
2246 if(v == VariantFischeRandom) type = "chess960"; else
2247 if(v == VariantLoadable) type = "setup"; else
2248 type = VariantName(v);
2250 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2251 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2252 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2253 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2254 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2255 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2256 seekNrList[nrOfSeekAds] = nr;
2257 zList[nrOfSeekAds] = 0;
2258 seekAdList[nrOfSeekAds++] = StrSave(buf);
2259 if(plot) PlotSeekAd(nrOfSeekAds-1);
2266 int x = xList[i], y = yList[i], d=squareSize/4, k;
2267 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2268 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2269 // now replot every dot that overlapped
2270 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2271 int xx = xList[k], yy = yList[k];
2272 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2273 DrawSeekDot(xx, yy, colorList[k]);
2278 RemoveSeekAd(int nr)
2281 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2283 if(seekAdList[i]) free(seekAdList[i]);
2284 seekAdList[i] = seekAdList[--nrOfSeekAds];
2285 seekNrList[i] = seekNrList[nrOfSeekAds];
2286 ratingList[i] = ratingList[nrOfSeekAds];
2287 colorList[i] = colorList[nrOfSeekAds];
2288 tcList[i] = tcList[nrOfSeekAds];
2289 xList[i] = xList[nrOfSeekAds];
2290 yList[i] = yList[nrOfSeekAds];
2291 zList[i] = zList[nrOfSeekAds];
2292 seekAdList[nrOfSeekAds] = NULL;
2298 MatchSoughtLine(char *line)
2300 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2301 int nr, base, inc, u=0; char dummy;
2303 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2304 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2306 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2307 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2308 // match: compact and save the line
2309 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2319 if(!seekGraphUp) return FALSE;
2320 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2321 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2323 DrawSeekBackground(0, 0, w, h);
2324 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2325 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2326 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2327 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2329 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2332 snprintf(buf, MSG_SIZ, "%d", i);
2333 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2336 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2337 for(i=1; i<100; i+=(i<10?1:5)) {
2338 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2339 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2340 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2342 snprintf(buf, MSG_SIZ, "%d", i);
2343 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2346 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2350 int SeekGraphClick(ClickType click, int x, int y, int moving)
2352 static int lastDown = 0, displayed = 0, lastSecond;
2353 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2354 if(click == Release || moving) return FALSE;
2356 soughtPending = TRUE;
2357 SendToICS(ics_prefix);
2358 SendToICS("sought\n"); // should this be "sought all"?
2359 } else { // issue challenge based on clicked ad
2360 int dist = 10000; int i, closest = 0, second = 0;
2361 for(i=0; i<nrOfSeekAds; i++) {
2362 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2363 if(d < dist) { dist = d; closest = i; }
2364 second += (d - zList[i] < 120); // count in-range ads
2365 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2369 second = (second > 1);
2370 if(displayed != closest || second != lastSecond) {
2371 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2372 lastSecond = second; displayed = closest;
2374 if(click == Press) {
2375 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2378 } // on press 'hit', only show info
2379 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2380 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2381 SendToICS(ics_prefix);
2383 return TRUE; // let incoming board of started game pop down the graph
2384 } else if(click == Release) { // release 'miss' is ignored
2385 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2386 if(moving == 2) { // right up-click
2387 nrOfSeekAds = 0; // refresh graph
2388 soughtPending = TRUE;
2389 SendToICS(ics_prefix);
2390 SendToICS("sought\n"); // should this be "sought all"?
2393 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2394 // press miss or release hit 'pop down' seek graph
2395 seekGraphUp = FALSE;
2396 DrawPosition(TRUE, NULL);
2402 read_from_ics(isr, closure, data, count, error)
2409 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2410 #define STARTED_NONE 0
2411 #define STARTED_MOVES 1
2412 #define STARTED_BOARD 2
2413 #define STARTED_OBSERVE 3
2414 #define STARTED_HOLDINGS 4
2415 #define STARTED_CHATTER 5
2416 #define STARTED_COMMENT 6
2417 #define STARTED_MOVES_NOHIDE 7
2419 static int started = STARTED_NONE;
2420 static char parse[20000];
2421 static int parse_pos = 0;
2422 static char buf[BUF_SIZE + 1];
2423 static int firstTime = TRUE, intfSet = FALSE;
2424 static ColorClass prevColor = ColorNormal;
2425 static int savingComment = FALSE;
2426 static int cmatch = 0; // continuation sequence match
2433 int backup; /* [DM] For zippy color lines */
2435 char talker[MSG_SIZ]; // [HGM] chat
2438 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2440 if (appData.debugMode) {
2442 fprintf(debugFP, "<ICS: ");
2443 show_bytes(debugFP, data, count);
2444 fprintf(debugFP, "\n");
2448 if (appData.debugMode) { int f = forwardMostMove;
2449 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2450 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2451 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2454 /* If last read ended with a partial line that we couldn't parse,
2455 prepend it to the new read and try again. */
2456 if (leftover_len > 0) {
2457 for (i=0; i<leftover_len; i++)
2458 buf[i] = buf[leftover_start + i];
2461 /* copy new characters into the buffer */
2462 bp = buf + leftover_len;
2463 buf_len=leftover_len;
2464 for (i=0; i<count; i++)
2467 if (data[i] == '\r')
2470 // join lines split by ICS?
2471 if (!appData.noJoin)
2474 Joining just consists of finding matches against the
2475 continuation sequence, and discarding that sequence
2476 if found instead of copying it. So, until a match
2477 fails, there's nothing to do since it might be the
2478 complete sequence, and thus, something we don't want
2481 if (data[i] == cont_seq[cmatch])
2484 if (cmatch == strlen(cont_seq))
2486 cmatch = 0; // complete match. just reset the counter
2489 it's possible for the ICS to not include the space
2490 at the end of the last word, making our [correct]
2491 join operation fuse two separate words. the server
2492 does this when the space occurs at the width setting.
2494 if (!buf_len || buf[buf_len-1] != ' ')
2505 match failed, so we have to copy what matched before
2506 falling through and copying this character. In reality,
2507 this will only ever be just the newline character, but
2508 it doesn't hurt to be precise.
2510 strncpy(bp, cont_seq, cmatch);
2522 buf[buf_len] = NULLCHAR;
2523 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2528 while (i < buf_len) {
2529 /* Deal with part of the TELNET option negotiation
2530 protocol. We refuse to do anything beyond the
2531 defaults, except that we allow the WILL ECHO option,
2532 which ICS uses to turn off password echoing when we are
2533 directly connected to it. We reject this option
2534 if localLineEditing mode is on (always on in xboard)
2535 and we are talking to port 23, which might be a real
2536 telnet server that will try to keep WILL ECHO on permanently.
2538 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2539 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2540 unsigned char option;
2542 switch ((unsigned char) buf[++i]) {
2544 if (appData.debugMode)
2545 fprintf(debugFP, "\n<WILL ");
2546 switch (option = (unsigned char) buf[++i]) {
2548 if (appData.debugMode)
2549 fprintf(debugFP, "ECHO ");
2550 /* Reply only if this is a change, according
2551 to the protocol rules. */
2552 if (remoteEchoOption) break;
2553 if (appData.localLineEditing &&
2554 atoi(appData.icsPort) == TN_PORT) {
2555 TelnetRequest(TN_DONT, TN_ECHO);
2558 TelnetRequest(TN_DO, TN_ECHO);
2559 remoteEchoOption = TRUE;
2563 if (appData.debugMode)
2564 fprintf(debugFP, "%d ", option);
2565 /* Whatever this is, we don't want it. */
2566 TelnetRequest(TN_DONT, option);
2571 if (appData.debugMode)
2572 fprintf(debugFP, "\n<WONT ");
2573 switch (option = (unsigned char) buf[++i]) {
2575 if (appData.debugMode)
2576 fprintf(debugFP, "ECHO ");
2577 /* Reply only if this is a change, according
2578 to the protocol rules. */
2579 if (!remoteEchoOption) break;
2581 TelnetRequest(TN_DONT, TN_ECHO);
2582 remoteEchoOption = FALSE;
2585 if (appData.debugMode)
2586 fprintf(debugFP, "%d ", (unsigned char) option);
2587 /* Whatever this is, it must already be turned
2588 off, because we never agree to turn on
2589 anything non-default, so according to the
2590 protocol rules, we don't reply. */
2595 if (appData.debugMode)
2596 fprintf(debugFP, "\n<DO ");
2597 switch (option = (unsigned char) buf[++i]) {
2599 /* Whatever this is, we refuse to do it. */
2600 if (appData.debugMode)
2601 fprintf(debugFP, "%d ", option);
2602 TelnetRequest(TN_WONT, option);
2607 if (appData.debugMode)
2608 fprintf(debugFP, "\n<DONT ");
2609 switch (option = (unsigned char) buf[++i]) {
2611 if (appData.debugMode)
2612 fprintf(debugFP, "%d ", option);
2613 /* Whatever this is, we are already not doing
2614 it, because we never agree to do anything
2615 non-default, so according to the protocol
2616 rules, we don't reply. */
2621 if (appData.debugMode)
2622 fprintf(debugFP, "\n<IAC ");
2623 /* Doubled IAC; pass it through */
2627 if (appData.debugMode)
2628 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2629 /* Drop all other telnet commands on the floor */
2632 if (oldi > next_out)
2633 SendToPlayer(&buf[next_out], oldi - next_out);
2639 /* OK, this at least will *usually* work */
2640 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2644 if (loggedOn && !intfSet) {
2645 if (ics_type == ICS_ICC) {
2646 snprintf(str, MSG_SIZ,
2647 "/set-quietly interface %s\n/set-quietly style 12\n",
2649 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2650 strcat(str, "/set-2 51 1\n/set seek 1\n");
2651 } else if (ics_type == ICS_CHESSNET) {
2652 snprintf(str, MSG_SIZ, "/style 12\n");
2654 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2655 strcat(str, programVersion);
2656 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2657 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2658 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2660 strcat(str, "$iset nohighlight 1\n");
2662 strcat(str, "$iset lock 1\n$style 12\n");
2665 NotifyFrontendLogin();
2669 if (started == STARTED_COMMENT) {
2670 /* Accumulate characters in comment */
2671 parse[parse_pos++] = buf[i];
2672 if (buf[i] == '\n') {
2673 parse[parse_pos] = NULLCHAR;
2674 if(chattingPartner>=0) {
2676 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2677 OutputChatMessage(chattingPartner, mess);
2678 chattingPartner = -1;
2679 next_out = i+1; // [HGM] suppress printing in ICS window
2681 if(!suppressKibitz) // [HGM] kibitz
2682 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2683 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2684 int nrDigit = 0, nrAlph = 0, j;
2685 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2686 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2687 parse[parse_pos] = NULLCHAR;
2688 // try to be smart: if it does not look like search info, it should go to
2689 // ICS interaction window after all, not to engine-output window.
2690 for(j=0; j<parse_pos; j++) { // count letters and digits
2691 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2692 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2693 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2695 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2696 int depth=0; float score;
2697 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2698 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2699 pvInfoList[forwardMostMove-1].depth = depth;
2700 pvInfoList[forwardMostMove-1].score = 100*score;
2702 OutputKibitz(suppressKibitz, parse);
2705 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2706 SendToPlayer(tmp, strlen(tmp));
2708 next_out = i+1; // [HGM] suppress printing in ICS window
2710 started = STARTED_NONE;
2712 /* Don't match patterns against characters in comment */
2717 if (started == STARTED_CHATTER) {
2718 if (buf[i] != '\n') {
2719 /* Don't match patterns against characters in chatter */
2723 started = STARTED_NONE;
2724 if(suppressKibitz) next_out = i+1;
2727 /* Kludge to deal with rcmd protocol */
2728 if (firstTime && looking_at(buf, &i, "\001*")) {
2729 DisplayFatalError(&buf[1], 0, 1);
2735 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2738 if (appData.debugMode)
2739 fprintf(debugFP, "ics_type %d\n", ics_type);
2742 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2743 ics_type = ICS_FICS;
2745 if (appData.debugMode)
2746 fprintf(debugFP, "ics_type %d\n", ics_type);
2749 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2750 ics_type = ICS_CHESSNET;
2752 if (appData.debugMode)
2753 fprintf(debugFP, "ics_type %d\n", ics_type);
2758 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2759 looking_at(buf, &i, "Logging you in as \"*\"") ||
2760 looking_at(buf, &i, "will be \"*\""))) {
2761 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2765 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2767 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2768 DisplayIcsInteractionTitle(buf);
2769 have_set_title = TRUE;
2772 /* skip finger notes */
2773 if (started == STARTED_NONE &&
2774 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2775 (buf[i] == '1' && buf[i+1] == '0')) &&
2776 buf[i+2] == ':' && buf[i+3] == ' ') {
2777 started = STARTED_CHATTER;
2783 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2784 if(appData.seekGraph) {
2785 if(soughtPending && MatchSoughtLine(buf+i)) {
2786 i = strstr(buf+i, "rated") - buf;
2787 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2788 next_out = leftover_start = i;
2789 started = STARTED_CHATTER;
2790 suppressKibitz = TRUE;
2793 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2794 && looking_at(buf, &i, "* ads displayed")) {
2795 soughtPending = FALSE;
2800 if(appData.autoRefresh) {
2801 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2802 int s = (ics_type == ICS_ICC); // ICC format differs
2804 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2805 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2806 looking_at(buf, &i, "*% "); // eat prompt
2807 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2808 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2809 next_out = i; // suppress
2812 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2813 char *p = star_match[0];
2815 if(seekGraphUp) RemoveSeekAd(atoi(p));
2816 while(*p && *p++ != ' '); // next
2818 looking_at(buf, &i, "*% "); // eat prompt
2819 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2826 /* skip formula vars */
2827 if (started == STARTED_NONE &&
2828 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2829 started = STARTED_CHATTER;
2834 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2835 if (appData.autoKibitz && started == STARTED_NONE &&
2836 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2837 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2838 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2839 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2840 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2841 suppressKibitz = TRUE;
2842 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2844 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2845 && (gameMode == IcsPlayingWhite)) ||
2846 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2847 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2848 started = STARTED_CHATTER; // own kibitz we simply discard
2850 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2851 parse_pos = 0; parse[0] = NULLCHAR;
2852 savingComment = TRUE;
2853 suppressKibitz = gameMode != IcsObserving ? 2 :
2854 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2858 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2859 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2860 && atoi(star_match[0])) {
2861 // suppress the acknowledgements of our own autoKibitz
2863 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2864 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2865 SendToPlayer(star_match[0], strlen(star_match[0]));
2866 if(looking_at(buf, &i, "*% ")) // eat prompt
2867 suppressKibitz = FALSE;
2871 } // [HGM] kibitz: end of patch
2873 // [HGM] chat: intercept tells by users for which we have an open chat window
2875 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2876 looking_at(buf, &i, "* whispers:") ||
2877 looking_at(buf, &i, "* kibitzes:") ||
2878 looking_at(buf, &i, "* shouts:") ||
2879 looking_at(buf, &i, "* c-shouts:") ||
2880 looking_at(buf, &i, "--> * ") ||
2881 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2882 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2883 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2884 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2886 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2887 chattingPartner = -1;
2889 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2890 for(p=0; p<MAX_CHAT; p++) {
2891 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2892 talker[0] = '['; strcat(talker, "] ");
2893 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2894 chattingPartner = p; break;
2897 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2898 for(p=0; p<MAX_CHAT; p++) {
2899 if(!strcmp("kibitzes", chatPartner[p])) {
2900 talker[0] = '['; strcat(talker, "] ");
2901 chattingPartner = p; break;
2904 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2905 for(p=0; p<MAX_CHAT; p++) {
2906 if(!strcmp("whispers", chatPartner[p])) {
2907 talker[0] = '['; strcat(talker, "] ");
2908 chattingPartner = p; break;
2911 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2912 if(buf[i-8] == '-' && buf[i-3] == 't')
2913 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2914 if(!strcmp("c-shouts", chatPartner[p])) {
2915 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2916 chattingPartner = p; break;
2919 if(chattingPartner < 0)
2920 for(p=0; p<MAX_CHAT; p++) {
2921 if(!strcmp("shouts", chatPartner[p])) {
2922 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2923 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2924 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2925 chattingPartner = p; break;
2929 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2930 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2931 talker[0] = 0; Colorize(ColorTell, FALSE);
2932 chattingPartner = p; break;
2934 if(chattingPartner<0) i = oldi; else {
2935 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2936 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2937 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2938 started = STARTED_COMMENT;
2939 parse_pos = 0; parse[0] = NULLCHAR;
2940 savingComment = 3 + chattingPartner; // counts as TRUE
2941 suppressKibitz = TRUE;
2944 } // [HGM] chat: end of patch
2946 if (appData.zippyTalk || appData.zippyPlay) {
2947 /* [DM] Backup address for color zippy lines */
2950 if (loggedOn == TRUE)
2951 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2952 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2954 } // [DM] 'else { ' deleted
2956 /* Regular tells and says */
2957 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2958 looking_at(buf, &i, "* (your partner) tells you: ") ||
2959 looking_at(buf, &i, "* says: ") ||
2960 /* Don't color "message" or "messages" output */
2961 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2962 looking_at(buf, &i, "*. * at *:*: ") ||
2963 looking_at(buf, &i, "--* (*:*): ") ||
2964 /* Message notifications (same color as tells) */
2965 looking_at(buf, &i, "* has left a message ") ||
2966 looking_at(buf, &i, "* just sent you a message:\n") ||
2967 /* Whispers and kibitzes */
2968 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2969 looking_at(buf, &i, "* kibitzes: ") ||
2971 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2973 if (tkind == 1 && strchr(star_match[0], ':')) {
2974 /* Avoid "tells you:" spoofs in channels */
2977 if (star_match[0][0] == NULLCHAR ||
2978 strchr(star_match[0], ' ') ||
2979 (tkind == 3 && strchr(star_match[1], ' '))) {
2980 /* Reject bogus matches */
2983 if (appData.colorize) {
2984 if (oldi > next_out) {
2985 SendToPlayer(&buf[next_out], oldi - next_out);
2990 Colorize(ColorTell, FALSE);
2991 curColor = ColorTell;
2994 Colorize(ColorKibitz, FALSE);
2995 curColor = ColorKibitz;
2998 p = strrchr(star_match[1], '(');
3005 Colorize(ColorChannel1, FALSE);
3006 curColor = ColorChannel1;
3008 Colorize(ColorChannel, FALSE);
3009 curColor = ColorChannel;
3013 curColor = ColorNormal;
3017 if (started == STARTED_NONE && appData.autoComment &&
3018 (gameMode == IcsObserving ||
3019 gameMode == IcsPlayingWhite ||
3020 gameMode == IcsPlayingBlack)) {
3021 parse_pos = i - oldi;
3022 memcpy(parse, &buf[oldi], parse_pos);
3023 parse[parse_pos] = NULLCHAR;
3024 started = STARTED_COMMENT;
3025 savingComment = TRUE;
3027 started = STARTED_CHATTER;
3028 savingComment = FALSE;
3035 if (looking_at(buf, &i, "* s-shouts: ") ||
3036 looking_at(buf, &i, "* c-shouts: ")) {
3037 if (appData.colorize) {
3038 if (oldi > next_out) {
3039 SendToPlayer(&buf[next_out], oldi - next_out);
3042 Colorize(ColorSShout, FALSE);
3043 curColor = ColorSShout;
3046 started = STARTED_CHATTER;
3050 if (looking_at(buf, &i, "--->")) {
3055 if (looking_at(buf, &i, "* shouts: ") ||
3056 looking_at(buf, &i, "--> ")) {
3057 if (appData.colorize) {
3058 if (oldi > next_out) {
3059 SendToPlayer(&buf[next_out], oldi - next_out);
3062 Colorize(ColorShout, FALSE);
3063 curColor = ColorShout;
3066 started = STARTED_CHATTER;
3070 if (looking_at( buf, &i, "Challenge:")) {
3071 if (appData.colorize) {
3072 if (oldi > next_out) {
3073 SendToPlayer(&buf[next_out], oldi - next_out);
3076 Colorize(ColorChallenge, FALSE);
3077 curColor = ColorChallenge;
3083 if (looking_at(buf, &i, "* offers you") ||
3084 looking_at(buf, &i, "* offers to be") ||
3085 looking_at(buf, &i, "* would like to") ||
3086 looking_at(buf, &i, "* requests to") ||
3087 looking_at(buf, &i, "Your opponent offers") ||
3088 looking_at(buf, &i, "Your opponent requests")) {
3090 if (appData.colorize) {
3091 if (oldi > next_out) {
3092 SendToPlayer(&buf[next_out], oldi - next_out);
3095 Colorize(ColorRequest, FALSE);
3096 curColor = ColorRequest;
3101 if (looking_at(buf, &i, "* (*) seeking")) {
3102 if (appData.colorize) {
3103 if (oldi > next_out) {
3104 SendToPlayer(&buf[next_out], oldi - next_out);
3107 Colorize(ColorSeek, FALSE);
3108 curColor = ColorSeek;
3113 if (looking_at(buf, &i, "\\ ")) {
3114 if (prevColor != ColorNormal) {
3115 if (oldi > next_out) {
3116 SendToPlayer(&buf[next_out], oldi - next_out);
3119 Colorize(prevColor, TRUE);
3120 curColor = prevColor;
3122 if (savingComment) {
3123 parse_pos = i - oldi;
3124 memcpy(parse, &buf[oldi], parse_pos);
3125 parse[parse_pos] = NULLCHAR;
3126 started = STARTED_COMMENT;
3127 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3128 chattingPartner = savingComment - 3; // kludge to remember the box
3130 started = STARTED_CHATTER;
3135 if (looking_at(buf, &i, "Black Strength :") ||
3136 looking_at(buf, &i, "<<< style 10 board >>>") ||
3137 looking_at(buf, &i, "<10>") ||
3138 looking_at(buf, &i, "#@#")) {
3139 /* Wrong board style */
3141 SendToICS(ics_prefix);
3142 SendToICS("set style 12\n");
3143 SendToICS(ics_prefix);
3144 SendToICS("refresh\n");
3148 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3150 have_sent_ICS_logon = 1;
3154 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3155 (looking_at(buf, &i, "\n<12> ") ||
3156 looking_at(buf, &i, "<12> "))) {
3158 if (oldi > next_out) {
3159 SendToPlayer(&buf[next_out], oldi - next_out);
3162 started = STARTED_BOARD;
3167 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3168 looking_at(buf, &i, "<b1> ")) {
3169 if (oldi > next_out) {
3170 SendToPlayer(&buf[next_out], oldi - next_out);
3173 started = STARTED_HOLDINGS;
3178 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3180 /* Header for a move list -- first line */
3182 switch (ics_getting_history) {
3186 case BeginningOfGame:
3187 /* User typed "moves" or "oldmoves" while we
3188 were idle. Pretend we asked for these
3189 moves and soak them up so user can step
3190 through them and/or save them.
3193 gameMode = IcsObserving;
3196 ics_getting_history = H_GOT_UNREQ_HEADER;
3198 case EditGame: /*?*/
3199 case EditPosition: /*?*/
3200 /* Should above feature work in these modes too? */
3201 /* For now it doesn't */
3202 ics_getting_history = H_GOT_UNWANTED_HEADER;
3205 ics_getting_history = H_GOT_UNWANTED_HEADER;
3210 /* Is this the right one? */
3211 if (gameInfo.white && gameInfo.black &&
3212 strcmp(gameInfo.white, star_match[0]) == 0 &&
3213 strcmp(gameInfo.black, star_match[2]) == 0) {
3215 ics_getting_history = H_GOT_REQ_HEADER;
3218 case H_GOT_REQ_HEADER:
3219 case H_GOT_UNREQ_HEADER:
3220 case H_GOT_UNWANTED_HEADER:
3221 case H_GETTING_MOVES:
3222 /* Should not happen */
3223 DisplayError(_("Error gathering move list: two headers"), 0);
3224 ics_getting_history = H_FALSE;
3228 /* Save player ratings into gameInfo if needed */
3229 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3230 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3231 (gameInfo.whiteRating == -1 ||
3232 gameInfo.blackRating == -1)) {
3234 gameInfo.whiteRating = string_to_rating(star_match[1]);
3235 gameInfo.blackRating = string_to_rating(star_match[3]);
3236 if (appData.debugMode)
3237 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3238 gameInfo.whiteRating, gameInfo.blackRating);
3243 if (looking_at(buf, &i,
3244 "* * match, initial time: * minute*, increment: * second")) {
3245 /* Header for a move list -- second line */
3246 /* Initial board will follow if this is a wild game */
3247 if (gameInfo.event != NULL) free(gameInfo.event);
3248 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3249 gameInfo.event = StrSave(str);
3250 /* [HGM] we switched variant. Translate boards if needed. */
3251 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3255 if (looking_at(buf, &i, "Move ")) {
3256 /* Beginning of a move list */
3257 switch (ics_getting_history) {
3259 /* Normally should not happen */
3260 /* Maybe user hit reset while we were parsing */
3263 /* Happens if we are ignoring a move list that is not
3264 * the one we just requested. Common if the user
3265 * tries to observe two games without turning off
3268 case H_GETTING_MOVES:
3269 /* Should not happen */
3270 DisplayError(_("Error gathering move list: nested"), 0);
3271 ics_getting_history = H_FALSE;
3273 case H_GOT_REQ_HEADER:
3274 ics_getting_history = H_GETTING_MOVES;
3275 started = STARTED_MOVES;
3277 if (oldi > next_out) {
3278 SendToPlayer(&buf[next_out], oldi - next_out);
3281 case H_GOT_UNREQ_HEADER:
3282 ics_getting_history = H_GETTING_MOVES;
3283 started = STARTED_MOVES_NOHIDE;
3286 case H_GOT_UNWANTED_HEADER:
3287 ics_getting_history = H_FALSE;
3293 if (looking_at(buf, &i, "% ") ||
3294 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3295 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3296 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3297 soughtPending = FALSE;
3301 if(suppressKibitz) next_out = i;
3302 savingComment = FALSE;
3306 case STARTED_MOVES_NOHIDE:
3307 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3308 parse[parse_pos + i - oldi] = NULLCHAR;
3309 ParseGameHistory(parse);
3311 if (appData.zippyPlay && first.initDone) {
3312 FeedMovesToProgram(&first, forwardMostMove);
3313 if (gameMode == IcsPlayingWhite) {
3314 if (WhiteOnMove(forwardMostMove)) {
3315 if (first.sendTime) {
3316 if (first.useColors) {
3317 SendToProgram("black\n", &first);
3319 SendTimeRemaining(&first, TRUE);
3321 if (first.useColors) {
3322 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3324 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3325 first.maybeThinking = TRUE;
3327 if (first.usePlayother) {
3328 if (first.sendTime) {
3329 SendTimeRemaining(&first, TRUE);
3331 SendToProgram("playother\n", &first);
3337 } else if (gameMode == IcsPlayingBlack) {
3338 if (!WhiteOnMove(forwardMostMove)) {
3339 if (first.sendTime) {
3340 if (first.useColors) {
3341 SendToProgram("white\n", &first);
3343 SendTimeRemaining(&first, FALSE);
3345 if (first.useColors) {
3346 SendToProgram("black\n", &first);
3348 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3349 first.maybeThinking = TRUE;
3351 if (first.usePlayother) {
3352 if (first.sendTime) {
3353 SendTimeRemaining(&first, FALSE);
3355 SendToProgram("playother\n", &first);
3364 if (gameMode == IcsObserving && ics_gamenum == -1) {
3365 /* Moves came from oldmoves or moves command
3366 while we weren't doing anything else.
3368 currentMove = forwardMostMove;
3369 ClearHighlights();/*!!could figure this out*/
3370 flipView = appData.flipView;
3371 DrawPosition(TRUE, boards[currentMove]);
3372 DisplayBothClocks();
3373 snprintf(str, MSG_SIZ, "%s vs. %s",
3374 gameInfo.white, gameInfo.black);
3378 /* Moves were history of an active game */
3379 if (gameInfo.resultDetails != NULL) {
3380 free(gameInfo.resultDetails);
3381 gameInfo.resultDetails = NULL;
3384 HistorySet(parseList, backwardMostMove,
3385 forwardMostMove, currentMove-1);
3386 DisplayMove(currentMove - 1);
3387 if (started == STARTED_MOVES) next_out = i;
3388 started = STARTED_NONE;
3389 ics_getting_history = H_FALSE;
3392 case STARTED_OBSERVE:
3393 started = STARTED_NONE;
3394 SendToICS(ics_prefix);
3395 SendToICS("refresh\n");
3401 if(bookHit) { // [HGM] book: simulate book reply
3402 static char bookMove[MSG_SIZ]; // a bit generous?
3404 programStats.nodes = programStats.depth = programStats.time =
3405 programStats.score = programStats.got_only_move = 0;
3406 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3408 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3409 strcat(bookMove, bookHit);
3410 HandleMachineMove(bookMove, &first);
3415 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3416 started == STARTED_HOLDINGS ||
3417 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3418 /* Accumulate characters in move list or board */
3419 parse[parse_pos++] = buf[i];
3422 /* Start of game messages. Mostly we detect start of game
3423 when the first board image arrives. On some versions
3424 of the ICS, though, we need to do a "refresh" after starting
3425 to observe in order to get the current board right away. */
3426 if (looking_at(buf, &i, "Adding game * to observation list")) {
3427 started = STARTED_OBSERVE;
3431 /* Handle auto-observe */
3432 if (appData.autoObserve &&
3433 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3434 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3436 /* Choose the player that was highlighted, if any. */
3437 if (star_match[0][0] == '\033' ||
3438 star_match[1][0] != '\033') {
3439 player = star_match[0];
3441 player = star_match[2];
3443 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3444 ics_prefix, StripHighlightAndTitle(player));
3447 /* Save ratings from notify string */
3448 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3449 player1Rating = string_to_rating(star_match[1]);
3450 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3451 player2Rating = string_to_rating(star_match[3]);
3453 if (appData.debugMode)
3455 "Ratings from 'Game notification:' %s %d, %s %d\n",
3456 player1Name, player1Rating,
3457 player2Name, player2Rating);
3462 /* Deal with automatic examine mode after a game,
3463 and with IcsObserving -> IcsExamining transition */
3464 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3465 looking_at(buf, &i, "has made you an examiner of game *")) {
3467 int gamenum = atoi(star_match[0]);
3468 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3469 gamenum == ics_gamenum) {
3470 /* We were already playing or observing this game;
3471 no need to refetch history */
3472 gameMode = IcsExamining;
3474 pauseExamForwardMostMove = forwardMostMove;
3475 } else if (currentMove < forwardMostMove) {
3476 ForwardInner(forwardMostMove);
3479 /* I don't think this case really can happen */
3480 SendToICS(ics_prefix);
3481 SendToICS("refresh\n");
3486 /* Error messages */
3487 // if (ics_user_moved) {
3488 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3489 if (looking_at(buf, &i, "Illegal move") ||
3490 looking_at(buf, &i, "Not a legal move") ||
3491 looking_at(buf, &i, "Your king is in check") ||
3492 looking_at(buf, &i, "It isn't your turn") ||
3493 looking_at(buf, &i, "It is not your move")) {
3495 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3496 currentMove = forwardMostMove-1;
3497 DisplayMove(currentMove - 1); /* before DMError */
3498 DrawPosition(FALSE, boards[currentMove]);
3499 SwitchClocks(forwardMostMove-1); // [HGM] race
3500 DisplayBothClocks();
3502 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3508 if (looking_at(buf, &i, "still have time") ||
3509 looking_at(buf, &i, "not out of time") ||
3510 looking_at(buf, &i, "either player is out of time") ||
3511 looking_at(buf, &i, "has timeseal; checking")) {
3512 /* We must have called his flag a little too soon */
3513 whiteFlag = blackFlag = FALSE;
3517 if (looking_at(buf, &i, "added * seconds to") ||
3518 looking_at(buf, &i, "seconds were added to")) {
3519 /* Update the clocks */
3520 SendToICS(ics_prefix);
3521 SendToICS("refresh\n");
3525 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3526 ics_clock_paused = TRUE;
3531 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3532 ics_clock_paused = FALSE;
3537 /* Grab player ratings from the Creating: message.
3538 Note we have to check for the special case when
3539 the ICS inserts things like [white] or [black]. */
3540 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3541 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3543 0 player 1 name (not necessarily white)
3545 2 empty, white, or black (IGNORED)
3546 3 player 2 name (not necessarily black)
3549 The names/ratings are sorted out when the game
3550 actually starts (below).
3552 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3553 player1Rating = string_to_rating(star_match[1]);
3554 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3555 player2Rating = string_to_rating(star_match[4]);
3557 if (appData.debugMode)
3559 "Ratings from 'Creating:' %s %d, %s %d\n",
3560 player1Name, player1Rating,
3561 player2Name, player2Rating);
3566 /* Improved generic start/end-of-game messages */
3567 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3568 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3569 /* If tkind == 0: */
3570 /* star_match[0] is the game number */
3571 /* [1] is the white player's name */
3572 /* [2] is the black player's name */
3573 /* For end-of-game: */
3574 /* [3] is the reason for the game end */
3575 /* [4] is a PGN end game-token, preceded by " " */
3576 /* For start-of-game: */
3577 /* [3] begins with "Creating" or "Continuing" */
3578 /* [4] is " *" or empty (don't care). */
3579 int gamenum = atoi(star_match[0]);
3580 char *whitename, *blackname, *why, *endtoken;
3581 ChessMove endtype = EndOfFile;
3584 whitename = star_match[1];
3585 blackname = star_match[2];
3586 why = star_match[3];
3587 endtoken = star_match[4];
3589 whitename = star_match[1];
3590 blackname = star_match[3];
3591 why = star_match[5];
3592 endtoken = star_match[6];
3595 /* Game start messages */
3596 if (strncmp(why, "Creating ", 9) == 0 ||
3597 strncmp(why, "Continuing ", 11) == 0) {
3598 gs_gamenum = gamenum;
3599 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3600 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3602 if (appData.zippyPlay) {
3603 ZippyGameStart(whitename, blackname);
3606 partnerBoardValid = FALSE; // [HGM] bughouse
3610 /* Game end messages */
3611 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3612 ics_gamenum != gamenum) {
3615 while (endtoken[0] == ' ') endtoken++;
3616 switch (endtoken[0]) {
3619 endtype = GameUnfinished;
3622 endtype = BlackWins;
3625 if (endtoken[1] == '/')
3626 endtype = GameIsDrawn;
3628 endtype = WhiteWins;
3631 GameEnds(endtype, why, GE_ICS);
3633 if (appData.zippyPlay && first.initDone) {
3634 ZippyGameEnd(endtype, why);
3635 if (first.pr == NULL) {
3636 /* Start the next process early so that we'll
3637 be ready for the next challenge */
3638 StartChessProgram(&first);
3640 /* Send "new" early, in case this command takes
3641 a long time to finish, so that we'll be ready
3642 for the next challenge. */
3643 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3647 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3651 if (looking_at(buf, &i, "Removing game * from observation") ||
3652 looking_at(buf, &i, "no longer observing game *") ||
3653 looking_at(buf, &i, "Game * (*) has no examiners")) {
3654 if (gameMode == IcsObserving &&
3655 atoi(star_match[0]) == ics_gamenum)
3657 /* icsEngineAnalyze */
3658 if (appData.icsEngineAnalyze) {
3665 ics_user_moved = FALSE;
3670 if (looking_at(buf, &i, "no longer examining game *")) {
3671 if (gameMode == IcsExamining &&
3672 atoi(star_match[0]) == ics_gamenum)
3676 ics_user_moved = FALSE;
3681 /* Advance leftover_start past any newlines we find,
3682 so only partial lines can get reparsed */
3683 if (looking_at(buf, &i, "\n")) {
3684 prevColor = curColor;
3685 if (curColor != ColorNormal) {
3686 if (oldi > next_out) {
3687 SendToPlayer(&buf[next_out], oldi - next_out);
3690 Colorize(ColorNormal, FALSE);
3691 curColor = ColorNormal;
3693 if (started == STARTED_BOARD) {
3694 started = STARTED_NONE;
3695 parse[parse_pos] = NULLCHAR;
3696 ParseBoard12(parse);
3699 /* Send premove here */
3700 if (appData.premove) {
3702 if (currentMove == 0 &&
3703 gameMode == IcsPlayingWhite &&
3704 appData.premoveWhite) {
3705 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3706 if (appData.debugMode)
3707 fprintf(debugFP, "Sending premove:\n");
3709 } else if (currentMove == 1 &&
3710 gameMode == IcsPlayingBlack &&
3711 appData.premoveBlack) {
3712 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3713 if (appData.debugMode)
3714 fprintf(debugFP, "Sending premove:\n");
3716 } else if (gotPremove) {
3718 ClearPremoveHighlights();
3719 if (appData.debugMode)
3720 fprintf(debugFP, "Sending premove:\n");
3721 UserMoveEvent(premoveFromX, premoveFromY,
3722 premoveToX, premoveToY,
3727 /* Usually suppress following prompt */
3728 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3729 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3730 if (looking_at(buf, &i, "*% ")) {
3731 savingComment = FALSE;
3736 } else if (started == STARTED_HOLDINGS) {
3738 char new_piece[MSG_SIZ];
3739 started = STARTED_NONE;
3740 parse[parse_pos] = NULLCHAR;
3741 if (appData.debugMode)
3742 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3743 parse, currentMove);
3744 if (sscanf(parse, " game %d", &gamenum) == 1) {
3745 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3746 if (gameInfo.variant == VariantNormal) {
3747 /* [HGM] We seem to switch variant during a game!
3748 * Presumably no holdings were displayed, so we have
3749 * to move the position two files to the right to
3750 * create room for them!
3752 VariantClass newVariant;
3753 switch(gameInfo.boardWidth) { // base guess on board width
3754 case 9: newVariant = VariantShogi; break;
3755 case 10: newVariant = VariantGreat; break;
3756 default: newVariant = VariantCrazyhouse; break;
3758 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3759 /* Get a move list just to see the header, which
3760 will tell us whether this is really bug or zh */
3761 if (ics_getting_history == H_FALSE) {
3762 ics_getting_history = H_REQUESTED;
3763 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3767 new_piece[0] = NULLCHAR;
3768 sscanf(parse, "game %d white [%s black [%s <- %s",
3769 &gamenum, white_holding, black_holding,
3771 white_holding[strlen(white_holding)-1] = NULLCHAR;
3772 black_holding[strlen(black_holding)-1] = NULLCHAR;
3773 /* [HGM] copy holdings to board holdings area */
3774 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3775 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3776 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3778 if (appData.zippyPlay && first.initDone) {
3779 ZippyHoldings(white_holding, black_holding,
3783 if (tinyLayout || smallLayout) {
3784 char wh[16], bh[16];
3785 PackHolding(wh, white_holding);
3786 PackHolding(bh, black_holding);
3787 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3788 gameInfo.white, gameInfo.black);
3790 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3791 gameInfo.white, white_holding,
3792 gameInfo.black, black_holding);
3794 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3795 DrawPosition(FALSE, boards[currentMove]);
3797 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3798 sscanf(parse, "game %d white [%s black [%s <- %s",
3799 &gamenum, white_holding, black_holding,
3801 white_holding[strlen(white_holding)-1] = NULLCHAR;
3802 black_holding[strlen(black_holding)-1] = NULLCHAR;
3803 /* [HGM] copy holdings to partner-board holdings area */
3804 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3805 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3806 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3807 if(partnerUp) DrawPosition(FALSE, partnerBoard);