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
814 ChessProgramState *savCps;
820 if(WaitForEngine(savCps, LoadEngine)) return;
821 CommonEngineInit(); // recalculate time odds
822 if(gameInfo.variant != StringToVariant(appData.variant)) {
823 // we changed variant when loading the engine; this forces us to reset
824 Reset(TRUE, savCps != &first);
825 EditGameEvent(); // for consistency with other path, as Reset changes mode
827 InitChessProgram(savCps, FALSE);
828 SendToProgram("force\n", savCps);
829 DisplayMessage("", "");
830 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
831 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
837 ReplaceEngine(ChessProgramState *cps, int n)
841 appData.noChessProgram = False;
842 appData.clockMode = True;
844 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
851 int matched, min, sec;
853 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
854 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
856 GetTimeMark(&programStartTime);
857 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
860 programStats.ok_to_send = 1;
861 programStats.seen_stat = 0;
864 * Initialize game list
870 * Internet chess server status
872 if (appData.icsActive) {
873 appData.matchMode = FALSE;
874 appData.matchGames = 0;
876 appData.noChessProgram = !appData.zippyPlay;
878 appData.zippyPlay = FALSE;
879 appData.zippyTalk = FALSE;
880 appData.noChessProgram = TRUE;
882 if (*appData.icsHelper != NULLCHAR) {
883 appData.useTelnet = TRUE;
884 appData.telnetProgram = appData.icsHelper;
887 appData.zippyTalk = appData.zippyPlay = FALSE;
890 /* [AS] Initialize pv info list [HGM] and game state */
894 for( i=0; i<=framePtr; i++ ) {
895 pvInfoList[i].depth = -1;
896 boards[i][EP_STATUS] = EP_NONE;
897 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
902 * Parse timeControl resource
904 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
905 appData.movesPerSession)) {
907 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
908 DisplayFatalError(buf, 0, 2);
912 * Parse searchTime resource
914 if (*appData.searchTime != NULLCHAR) {
915 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
917 searchTime = min * 60;
918 } else if (matched == 2) {
919 searchTime = min * 60 + sec;
922 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
923 DisplayFatalError(buf, 0, 2);
927 /* [AS] Adjudication threshold */
928 adjudicateLossThreshold = appData.adjudicateLossThreshold;
930 InitEngine(&first, 0);
931 InitEngine(&second, 1);
934 if (appData.icsActive) {
935 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
936 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
937 appData.clockMode = FALSE;
938 first.sendTime = second.sendTime = 0;
942 /* Override some settings from environment variables, for backward
943 compatibility. Unfortunately it's not feasible to have the env
944 vars just set defaults, at least in xboard. Ugh.
946 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
951 if (!appData.icsActive) {
955 /* Check for variants that are supported only in ICS mode,
956 or not at all. Some that are accepted here nevertheless
957 have bugs; see comments below.
959 VariantClass variant = StringToVariant(appData.variant);
961 case VariantBughouse: /* need four players and two boards */
962 case VariantKriegspiel: /* need to hide pieces and move details */
963 /* case VariantFischeRandom: (Fabien: moved below) */
964 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
965 if( (len > MSG_SIZ) && appData.debugMode )
966 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
968 DisplayFatalError(buf, 0, 2);
972 case VariantLoadable:
982 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
983 if( (len > MSG_SIZ) && appData.debugMode )
984 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
986 DisplayFatalError(buf, 0, 2);
989 case VariantXiangqi: /* [HGM] repetition rules not implemented */
990 case VariantFairy: /* [HGM] TestLegality definitely off! */
991 case VariantGothic: /* [HGM] should work */
992 case VariantCapablanca: /* [HGM] should work */
993 case VariantCourier: /* [HGM] initial forced moves not implemented */
994 case VariantShogi: /* [HGM] could still mate with pawn drop */
995 case VariantKnightmate: /* [HGM] should work */
996 case VariantCylinder: /* [HGM] untested */
997 case VariantFalcon: /* [HGM] untested */
998 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
999 offboard interposition not understood */
1000 case VariantNormal: /* definitely works! */
1001 case VariantWildCastle: /* pieces not automatically shuffled */
1002 case VariantNoCastle: /* pieces not automatically shuffled */
1003 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1004 case VariantLosers: /* should work except for win condition,
1005 and doesn't know captures are mandatory */
1006 case VariantSuicide: /* should work except for win condition,
1007 and doesn't know captures are mandatory */
1008 case VariantGiveaway: /* should work except for win condition,
1009 and doesn't know captures are mandatory */
1010 case VariantTwoKings: /* should work */
1011 case VariantAtomic: /* should work except for win condition */
1012 case Variant3Check: /* should work except for win condition */
1013 case VariantShatranj: /* should work except for all win conditions */
1014 case VariantMakruk: /* should work except for daw countdown */
1015 case VariantBerolina: /* might work if TestLegality is off */
1016 case VariantCapaRandom: /* should work */
1017 case VariantJanus: /* should work */
1018 case VariantSuper: /* experimental */
1019 case VariantGreat: /* experimental, requires legality testing to be off */
1020 case VariantSChess: /* S-Chess, should work */
1021 case VariantSpartan: /* should work */
1028 int NextIntegerFromString( char ** str, long * value )
1033 while( *s == ' ' || *s == '\t' ) {
1039 if( *s >= '0' && *s <= '9' ) {
1040 while( *s >= '0' && *s <= '9' ) {
1041 *value = *value * 10 + (*s - '0');
1053 int NextTimeControlFromString( char ** str, long * value )
1056 int result = NextIntegerFromString( str, &temp );
1059 *value = temp * 60; /* Minutes */
1060 if( **str == ':' ) {
1062 result = NextIntegerFromString( str, &temp );
1063 *value += temp; /* Seconds */
1070 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1071 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1072 int result = -1, type = 0; long temp, temp2;
1074 if(**str != ':') return -1; // old params remain in force!
1076 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1077 if( NextIntegerFromString( str, &temp ) ) return -1;
1078 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1081 /* time only: incremental or sudden-death time control */
1082 if(**str == '+') { /* increment follows; read it */
1084 if(**str == '!') type = *(*str)++; // Bronstein TC
1085 if(result = NextIntegerFromString( str, &temp2)) return -1;
1086 *inc = temp2 * 1000;
1087 if(**str == '.') { // read fraction of increment
1088 char *start = ++(*str);
1089 if(result = NextIntegerFromString( str, &temp2)) return -1;
1091 while(start++ < *str) temp2 /= 10;
1095 *moves = 0; *tc = temp * 1000; *incType = type;
1099 (*str)++; /* classical time control */
1100 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1111 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1112 { /* [HGM] get time to add from the multi-session time-control string */
1113 int incType, moves=1; /* kludge to force reading of first session */
1114 long time, increment;
1117 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1118 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1120 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1121 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1122 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1123 if(movenr == -1) return time; /* last move before new session */
1124 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1125 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1126 if(!moves) return increment; /* current session is incremental */
1127 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1128 } while(movenr >= -1); /* try again for next session */
1130 return 0; // no new time quota on this move
1134 ParseTimeControl(tc, ti, mps)
1141 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1144 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1145 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1146 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1150 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1152 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1155 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1157 snprintf(buf, MSG_SIZ, ":%s", mytc);
1159 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1161 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1166 /* Parse second time control */
1169 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1177 timeControl_2 = tc2 * 1000;
1187 timeControl = tc1 * 1000;
1190 timeIncrement = ti * 1000; /* convert to ms */
1191 movesPerSession = 0;
1194 movesPerSession = mps;
1202 if (appData.debugMode) {
1203 fprintf(debugFP, "%s\n", programVersion);
1206 set_cont_sequence(appData.wrapContSeq);
1207 if (appData.matchGames > 0) {
1208 appData.matchMode = TRUE;
1209 } else if (appData.matchMode) {
1210 appData.matchGames = 1;
1212 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1213 appData.matchGames = appData.sameColorGames;
1214 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1215 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1216 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1219 if (appData.noChessProgram || first.protocolVersion == 1) {
1222 /* kludge: allow timeout for initial "feature" commands */
1224 DisplayMessage("", _("Starting chess program"));
1225 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1230 MatchEvent(int mode)
1231 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1232 /* Set up machine vs. machine match */
1233 if (appData.noChessProgram) {
1234 DisplayFatalError(_("Can't have a match with no chess programs"),
1240 if (*appData.loadGameFile != NULLCHAR) {
1241 int index = appData.loadGameIndex; // [HGM] autoinc
1242 if(index<0) lastIndex = index = 1;
1243 if (!LoadGameFromFile(appData.loadGameFile,
1245 appData.loadGameFile, FALSE)) {
1246 DisplayFatalError(_("Bad game file"), 0, 1);
1249 } else if (*appData.loadPositionFile != NULLCHAR) {
1250 int index = appData.loadPositionIndex; // [HGM] autoinc
1251 if(index<0) lastIndex = index = 1;
1252 if (!LoadPositionFromFile(appData.loadPositionFile,
1254 appData.loadPositionFile)) {
1255 DisplayFatalError(_("Bad position file"), 0, 1);
1259 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1264 InitBackEnd3 P((void))
1266 GameMode initialMode;
1270 InitChessProgram(&first, startedFromSetupPosition);
1272 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1273 free(programVersion);
1274 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1275 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1278 if (appData.icsActive) {
1280 /* [DM] Make a console window if needed [HGM] merged ifs */
1286 if (*appData.icsCommPort != NULLCHAR)
1287 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1288 appData.icsCommPort);
1290 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1291 appData.icsHost, appData.icsPort);
1293 if( (len > MSG_SIZ) && appData.debugMode )
1294 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1296 DisplayFatalError(buf, err, 1);
1301 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1303 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1304 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1305 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1306 } else if (appData.noChessProgram) {
1312 if (*appData.cmailGameName != NULLCHAR) {
1314 OpenLoopback(&cmailPR);
1316 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1320 DisplayMessage("", "");
1321 if (StrCaseCmp(appData.initialMode, "") == 0) {
1322 initialMode = BeginningOfGame;
1323 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1324 initialMode = TwoMachinesPlay;
1325 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1326 initialMode = AnalyzeFile;
1327 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1328 initialMode = AnalyzeMode;
1329 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1330 initialMode = MachinePlaysWhite;
1331 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1332 initialMode = MachinePlaysBlack;
1333 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1334 initialMode = EditGame;
1335 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1336 initialMode = EditPosition;
1337 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1338 initialMode = Training;
1340 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1341 if( (len > MSG_SIZ) && appData.debugMode )
1342 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1344 DisplayFatalError(buf, 0, 2);
1348 if (appData.matchMode) {
1350 } else if (*appData.cmailGameName != NULLCHAR) {
1351 /* Set up cmail mode */
1352 ReloadCmailMsgEvent(TRUE);
1354 /* Set up other modes */
1355 if (initialMode == AnalyzeFile) {
1356 if (*appData.loadGameFile == NULLCHAR) {
1357 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1361 if (*appData.loadGameFile != NULLCHAR) {
1362 (void) LoadGameFromFile(appData.loadGameFile,
1363 appData.loadGameIndex,
1364 appData.loadGameFile, TRUE);
1365 } else if (*appData.loadPositionFile != NULLCHAR) {
1366 (void) LoadPositionFromFile(appData.loadPositionFile,
1367 appData.loadPositionIndex,
1368 appData.loadPositionFile);
1369 /* [HGM] try to make self-starting even after FEN load */
1370 /* to allow automatic setup of fairy variants with wtm */
1371 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1372 gameMode = BeginningOfGame;
1373 setboardSpoiledMachineBlack = 1;
1375 /* [HGM] loadPos: make that every new game uses the setup */
1376 /* from file as long as we do not switch variant */
1377 if(!blackPlaysFirst) {
1378 startedFromPositionFile = TRUE;
1379 CopyBoard(filePosition, boards[0]);
1382 if (initialMode == AnalyzeMode) {
1383 if (appData.noChessProgram) {
1384 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1387 if (appData.icsActive) {
1388 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1392 } else if (initialMode == AnalyzeFile) {
1393 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1394 ShowThinkingEvent();
1396 AnalysisPeriodicEvent(1);
1397 } else if (initialMode == MachinePlaysWhite) {
1398 if (appData.noChessProgram) {
1399 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1403 if (appData.icsActive) {
1404 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1408 MachineWhiteEvent();
1409 } else if (initialMode == MachinePlaysBlack) {
1410 if (appData.noChessProgram) {
1411 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1415 if (appData.icsActive) {
1416 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1420 MachineBlackEvent();
1421 } else if (initialMode == TwoMachinesPlay) {
1422 if (appData.noChessProgram) {
1423 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1427 if (appData.icsActive) {
1428 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1433 } else if (initialMode == EditGame) {
1435 } else if (initialMode == EditPosition) {
1436 EditPositionEvent();
1437 } else if (initialMode == Training) {
1438 if (*appData.loadGameFile == NULLCHAR) {
1439 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1448 * Establish will establish a contact to a remote host.port.
1449 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1450 * used to talk to the host.
1451 * Returns 0 if okay, error code if not.
1458 if (*appData.icsCommPort != NULLCHAR) {
1459 /* Talk to the host through a serial comm port */
1460 return OpenCommPort(appData.icsCommPort, &icsPR);
1462 } else if (*appData.gateway != NULLCHAR) {
1463 if (*appData.remoteShell == NULLCHAR) {
1464 /* Use the rcmd protocol to run telnet program on a gateway host */
1465 snprintf(buf, sizeof(buf), "%s %s %s",
1466 appData.telnetProgram, appData.icsHost, appData.icsPort);
1467 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1470 /* Use the rsh program to run telnet program on a gateway host */
1471 if (*appData.remoteUser == NULLCHAR) {
1472 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1473 appData.gateway, appData.telnetProgram,
1474 appData.icsHost, appData.icsPort);
1476 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1477 appData.remoteShell, appData.gateway,
1478 appData.remoteUser, appData.telnetProgram,
1479 appData.icsHost, appData.icsPort);
1481 return StartChildProcess(buf, "", &icsPR);
1484 } else if (appData.useTelnet) {
1485 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1488 /* TCP socket interface differs somewhat between
1489 Unix and NT; handle details in the front end.
1491 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1495 void EscapeExpand(char *p, char *q)
1496 { // [HGM] initstring: routine to shape up string arguments
1497 while(*p++ = *q++) if(p[-1] == '\\')
1499 case 'n': p[-1] = '\n'; break;
1500 case 'r': p[-1] = '\r'; break;
1501 case 't': p[-1] = '\t'; break;
1502 case '\\': p[-1] = '\\'; break;
1503 case 0: *p = 0; return;
1504 default: p[-1] = q[-1]; break;
1509 show_bytes(fp, buf, count)
1515 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1516 fprintf(fp, "\\%03o", *buf & 0xff);
1525 /* Returns an errno value */
1527 OutputMaybeTelnet(pr, message, count, outError)
1533 char buf[8192], *p, *q, *buflim;
1534 int left, newcount, outcount;
1536 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1537 *appData.gateway != NULLCHAR) {
1538 if (appData.debugMode) {
1539 fprintf(debugFP, ">ICS: ");
1540 show_bytes(debugFP, message, count);
1541 fprintf(debugFP, "\n");
1543 return OutputToProcess(pr, message, count, outError);
1546 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1553 if (appData.debugMode) {
1554 fprintf(debugFP, ">ICS: ");
1555 show_bytes(debugFP, buf, newcount);
1556 fprintf(debugFP, "\n");
1558 outcount = OutputToProcess(pr, buf, newcount, outError);
1559 if (outcount < newcount) return -1; /* to be sure */
1566 } else if (((unsigned char) *p) == TN_IAC) {
1567 *q++ = (char) TN_IAC;
1574 if (appData.debugMode) {
1575 fprintf(debugFP, ">ICS: ");
1576 show_bytes(debugFP, buf, newcount);
1577 fprintf(debugFP, "\n");
1579 outcount = OutputToProcess(pr, buf, newcount, outError);
1580 if (outcount < newcount) return -1; /* to be sure */
1585 read_from_player(isr, closure, message, count, error)
1592 int outError, outCount;
1593 static int gotEof = 0;
1595 /* Pass data read from player on to ICS */
1598 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1599 if (outCount < count) {
1600 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1602 } else if (count < 0) {
1603 RemoveInputSource(isr);
1604 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1605 } else if (gotEof++ > 0) {
1606 RemoveInputSource(isr);
1607 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1613 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1614 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1615 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1616 SendToICS("date\n");
1617 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1620 /* added routine for printf style output to ics */
1621 void ics_printf(char *format, ...)
1623 char buffer[MSG_SIZ];
1626 va_start(args, format);
1627 vsnprintf(buffer, sizeof(buffer), format, args);
1628 buffer[sizeof(buffer)-1] = '\0';
1637 int count, outCount, outError;
1639 if (icsPR == NULL) return;
1642 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1643 if (outCount < count) {
1644 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1648 /* This is used for sending logon scripts to the ICS. Sending
1649 without a delay causes problems when using timestamp on ICC
1650 (at least on my machine). */
1652 SendToICSDelayed(s,msdelay)
1656 int count, outCount, outError;
1658 if (icsPR == NULL) return;
1661 if (appData.debugMode) {
1662 fprintf(debugFP, ">ICS: ");
1663 show_bytes(debugFP, s, count);
1664 fprintf(debugFP, "\n");
1666 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1668 if (outCount < count) {
1669 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1674 /* Remove all highlighting escape sequences in s
1675 Also deletes any suffix starting with '('
1678 StripHighlightAndTitle(s)
1681 static char retbuf[MSG_SIZ];
1684 while (*s != NULLCHAR) {
1685 while (*s == '\033') {
1686 while (*s != NULLCHAR && !isalpha(*s)) s++;
1687 if (*s != NULLCHAR) s++;
1689 while (*s != NULLCHAR && *s != '\033') {
1690 if (*s == '(' || *s == '[') {
1701 /* Remove all highlighting escape sequences in s */
1706 static char retbuf[MSG_SIZ];
1709 while (*s != NULLCHAR) {
1710 while (*s == '\033') {
1711 while (*s != NULLCHAR && !isalpha(*s)) s++;
1712 if (*s != NULLCHAR) s++;
1714 while (*s != NULLCHAR && *s != '\033') {
1722 char *variantNames[] = VARIANT_NAMES;
1727 return variantNames[v];
1731 /* Identify a variant from the strings the chess servers use or the
1732 PGN Variant tag names we use. */
1739 VariantClass v = VariantNormal;
1740 int i, found = FALSE;
1746 /* [HGM] skip over optional board-size prefixes */
1747 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1748 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1749 while( *e++ != '_');
1752 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1756 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1757 if (StrCaseStr(e, variantNames[i])) {
1758 v = (VariantClass) i;
1765 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1766 || StrCaseStr(e, "wild/fr")
1767 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1768 v = VariantFischeRandom;
1769 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1770 (i = 1, p = StrCaseStr(e, "w"))) {
1772 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1779 case 0: /* FICS only, actually */
1781 /* Castling legal even if K starts on d-file */
1782 v = VariantWildCastle;
1787 /* Castling illegal even if K & R happen to start in
1788 normal positions. */
1789 v = VariantNoCastle;
1802 /* Castling legal iff K & R start in normal positions */
1808 /* Special wilds for position setup; unclear what to do here */
1809 v = VariantLoadable;
1812 /* Bizarre ICC game */
1813 v = VariantTwoKings;
1816 v = VariantKriegspiel;
1822 v = VariantFischeRandom;
1825 v = VariantCrazyhouse;
1828 v = VariantBughouse;
1834 /* Not quite the same as FICS suicide! */
1835 v = VariantGiveaway;
1841 v = VariantShatranj;
1844 /* Temporary names for future ICC types. The name *will* change in
1845 the next xboard/WinBoard release after ICC defines it. */
1883 v = VariantCapablanca;
1886 v = VariantKnightmate;
1892 v = VariantCylinder;
1898 v = VariantCapaRandom;
1901 v = VariantBerolina;
1913 /* Found "wild" or "w" in the string but no number;
1914 must assume it's normal chess. */
1918 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1919 if( (len > MSG_SIZ) && appData.debugMode )
1920 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1922 DisplayError(buf, 0);
1928 if (appData.debugMode) {
1929 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1930 e, wnum, VariantName(v));
1935 static int leftover_start = 0, leftover_len = 0;
1936 char star_match[STAR_MATCH_N][MSG_SIZ];
1938 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1939 advance *index beyond it, and set leftover_start to the new value of
1940 *index; else return FALSE. If pattern contains the character '*', it
1941 matches any sequence of characters not containing '\r', '\n', or the
1942 character following the '*' (if any), and the matched sequence(s) are
1943 copied into star_match.
1946 looking_at(buf, index, pattern)
1951 char *bufp = &buf[*index], *patternp = pattern;
1953 char *matchp = star_match[0];
1956 if (*patternp == NULLCHAR) {
1957 *index = leftover_start = bufp - buf;
1961 if (*bufp == NULLCHAR) return FALSE;
1962 if (*patternp == '*') {
1963 if (*bufp == *(patternp + 1)) {
1965 matchp = star_match[++star_count];
1969 } else if (*bufp == '\n' || *bufp == '\r') {
1971 if (*patternp == NULLCHAR)
1976 *matchp++ = *bufp++;
1980 if (*patternp != *bufp) return FALSE;
1987 SendToPlayer(data, length)
1991 int error, outCount;
1992 outCount = OutputToProcess(NoProc, data, length, &error);
1993 if (outCount < length) {
1994 DisplayFatalError(_("Error writing to display"), error, 1);
1999 PackHolding(packed, holding)
2011 switch (runlength) {
2022 sprintf(q, "%d", runlength);
2034 /* Telnet protocol requests from the front end */
2036 TelnetRequest(ddww, option)
2037 unsigned char ddww, option;
2039 unsigned char msg[3];
2040 int outCount, outError;
2042 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2044 if (appData.debugMode) {
2045 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2061 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2070 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2073 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2078 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2080 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2087 if (!appData.icsActive) return;
2088 TelnetRequest(TN_DO, TN_ECHO);
2094 if (!appData.icsActive) return;
2095 TelnetRequest(TN_DONT, TN_ECHO);
2099 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2101 /* put the holdings sent to us by the server on the board holdings area */
2102 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2106 if(gameInfo.holdingsWidth < 2) return;
2107 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2108 return; // prevent overwriting by pre-board holdings
2110 if( (int)lowestPiece >= BlackPawn ) {
2113 holdingsStartRow = BOARD_HEIGHT-1;
2116 holdingsColumn = BOARD_WIDTH-1;
2117 countsColumn = BOARD_WIDTH-2;
2118 holdingsStartRow = 0;
2122 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2123 board[i][holdingsColumn] = EmptySquare;
2124 board[i][countsColumn] = (ChessSquare) 0;
2126 while( (p=*holdings++) != NULLCHAR ) {
2127 piece = CharToPiece( ToUpper(p) );
2128 if(piece == EmptySquare) continue;
2129 /*j = (int) piece - (int) WhitePawn;*/
2130 j = PieceToNumber(piece);
2131 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2132 if(j < 0) continue; /* should not happen */
2133 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2134 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2135 board[holdingsStartRow+j*direction][countsColumn]++;
2141 VariantSwitch(Board board, VariantClass newVariant)
2143 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2144 static Board oldBoard;
2146 startedFromPositionFile = FALSE;
2147 if(gameInfo.variant == newVariant) return;
2149 /* [HGM] This routine is called each time an assignment is made to
2150 * gameInfo.variant during a game, to make sure the board sizes
2151 * are set to match the new variant. If that means adding or deleting
2152 * holdings, we shift the playing board accordingly
2153 * This kludge is needed because in ICS observe mode, we get boards
2154 * of an ongoing game without knowing the variant, and learn about the
2155 * latter only later. This can be because of the move list we requested,
2156 * in which case the game history is refilled from the beginning anyway,
2157 * but also when receiving holdings of a crazyhouse game. In the latter
2158 * case we want to add those holdings to the already received position.
2162 if (appData.debugMode) {
2163 fprintf(debugFP, "Switch board from %s to %s\n",
2164 VariantName(gameInfo.variant), VariantName(newVariant));
2165 setbuf(debugFP, NULL);
2167 shuffleOpenings = 0; /* [HGM] shuffle */
2168 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2172 newWidth = 9; newHeight = 9;
2173 gameInfo.holdingsSize = 7;
2174 case VariantBughouse:
2175 case VariantCrazyhouse:
2176 newHoldingsWidth = 2; break;
2180 newHoldingsWidth = 2;
2181 gameInfo.holdingsSize = 8;
2184 case VariantCapablanca:
2185 case VariantCapaRandom:
2188 newHoldingsWidth = gameInfo.holdingsSize = 0;
2191 if(newWidth != gameInfo.boardWidth ||
2192 newHeight != gameInfo.boardHeight ||
2193 newHoldingsWidth != gameInfo.holdingsWidth ) {
2195 /* shift position to new playing area, if needed */
2196 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2197 for(i=0; i<BOARD_HEIGHT; i++)
2198 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2199 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2201 for(i=0; i<newHeight; i++) {
2202 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2203 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2205 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2206 for(i=0; i<BOARD_HEIGHT; i++)
2207 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2208 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2211 gameInfo.boardWidth = newWidth;
2212 gameInfo.boardHeight = newHeight;
2213 gameInfo.holdingsWidth = newHoldingsWidth;
2214 gameInfo.variant = newVariant;
2215 InitDrawingSizes(-2, 0);
2216 } else gameInfo.variant = newVariant;
2217 CopyBoard(oldBoard, board); // remember correctly formatted board
2218 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2219 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2222 static int loggedOn = FALSE;
2224 /*-- Game start info cache: --*/
2226 char gs_kind[MSG_SIZ];
2227 static char player1Name[128] = "";
2228 static char player2Name[128] = "";
2229 static char cont_seq[] = "\n\\ ";
2230 static int player1Rating = -1;
2231 static int player2Rating = -1;
2232 /*----------------------------*/
2234 ColorClass curColor = ColorNormal;
2235 int suppressKibitz = 0;
2238 Boolean soughtPending = FALSE;
2239 Boolean seekGraphUp;
2240 #define MAX_SEEK_ADS 200
2242 char *seekAdList[MAX_SEEK_ADS];
2243 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2244 float tcList[MAX_SEEK_ADS];
2245 char colorList[MAX_SEEK_ADS];
2246 int nrOfSeekAds = 0;
2247 int minRating = 1010, maxRating = 2800;
2248 int hMargin = 10, vMargin = 20, h, w;
2249 extern int squareSize, lineGap;
2254 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2255 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2256 if(r < minRating+100 && r >=0 ) r = minRating+100;
2257 if(r > maxRating) r = maxRating;
2258 if(tc < 1.) tc = 1.;
2259 if(tc > 95.) tc = 95.;
2260 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2261 y = ((double)r - minRating)/(maxRating - minRating)
2262 * (h-vMargin-squareSize/8-1) + vMargin;
2263 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2264 if(strstr(seekAdList[i], " u ")) color = 1;
2265 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2266 !strstr(seekAdList[i], "bullet") &&
2267 !strstr(seekAdList[i], "blitz") &&
2268 !strstr(seekAdList[i], "standard") ) color = 2;
2269 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2270 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2274 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2276 char buf[MSG_SIZ], *ext = "";
2277 VariantClass v = StringToVariant(type);
2278 if(strstr(type, "wild")) {
2279 ext = type + 4; // append wild number
2280 if(v == VariantFischeRandom) type = "chess960"; else
2281 if(v == VariantLoadable) type = "setup"; else
2282 type = VariantName(v);
2284 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2285 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2286 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2287 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2288 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2289 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2290 seekNrList[nrOfSeekAds] = nr;
2291 zList[nrOfSeekAds] = 0;
2292 seekAdList[nrOfSeekAds++] = StrSave(buf);
2293 if(plot) PlotSeekAd(nrOfSeekAds-1);
2300 int x = xList[i], y = yList[i], d=squareSize/4, k;
2301 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2302 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2303 // now replot every dot that overlapped
2304 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2305 int xx = xList[k], yy = yList[k];
2306 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2307 DrawSeekDot(xx, yy, colorList[k]);
2312 RemoveSeekAd(int nr)
2315 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2317 if(seekAdList[i]) free(seekAdList[i]);
2318 seekAdList[i] = seekAdList[--nrOfSeekAds];
2319 seekNrList[i] = seekNrList[nrOfSeekAds];
2320 ratingList[i] = ratingList[nrOfSeekAds];
2321 colorList[i] = colorList[nrOfSeekAds];
2322 tcList[i] = tcList[nrOfSeekAds];
2323 xList[i] = xList[nrOfSeekAds];
2324 yList[i] = yList[nrOfSeekAds];
2325 zList[i] = zList[nrOfSeekAds];
2326 seekAdList[nrOfSeekAds] = NULL;
2332 MatchSoughtLine(char *line)
2334 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2335 int nr, base, inc, u=0; char dummy;
2337 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2338 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2340 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2341 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2342 // match: compact and save the line
2343 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2353 if(!seekGraphUp) return FALSE;
2354 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2355 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2357 DrawSeekBackground(0, 0, w, h);
2358 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2359 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2360 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2361 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2363 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2366 snprintf(buf, MSG_SIZ, "%d", i);
2367 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2370 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2371 for(i=1; i<100; i+=(i<10?1:5)) {
2372 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2373 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2374 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2376 snprintf(buf, MSG_SIZ, "%d", i);
2377 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2380 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2384 int SeekGraphClick(ClickType click, int x, int y, int moving)
2386 static int lastDown = 0, displayed = 0, lastSecond;
2387 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2388 if(click == Release || moving) return FALSE;
2390 soughtPending = TRUE;
2391 SendToICS(ics_prefix);
2392 SendToICS("sought\n"); // should this be "sought all"?
2393 } else { // issue challenge based on clicked ad
2394 int dist = 10000; int i, closest = 0, second = 0;
2395 for(i=0; i<nrOfSeekAds; i++) {
2396 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2397 if(d < dist) { dist = d; closest = i; }
2398 second += (d - zList[i] < 120); // count in-range ads
2399 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2403 second = (second > 1);
2404 if(displayed != closest || second != lastSecond) {
2405 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2406 lastSecond = second; displayed = closest;
2408 if(click == Press) {
2409 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2412 } // on press 'hit', only show info
2413 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2414 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2415 SendToICS(ics_prefix);
2417 return TRUE; // let incoming board of started game pop down the graph
2418 } else if(click == Release) { // release 'miss' is ignored
2419 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2420 if(moving == 2) { // right up-click
2421 nrOfSeekAds = 0; // refresh graph
2422 soughtPending = TRUE;
2423 SendToICS(ics_prefix);
2424 SendToICS("sought\n"); // should this be "sought all"?
2427 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2428 // press miss or release hit 'pop down' seek graph
2429 seekGraphUp = FALSE;
2430 DrawPosition(TRUE, NULL);
2436 read_from_ics(isr, closure, data, count, error)
2443 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2444 #define STARTED_NONE 0
2445 #define STARTED_MOVES 1
2446 #define STARTED_BOARD 2
2447 #define STARTED_OBSERVE 3
2448 #define STARTED_HOLDINGS 4
2449 #define STARTED_CHATTER 5
2450 #define STARTED_COMMENT 6
2451 #define STARTED_MOVES_NOHIDE 7
2453 static int started = STARTED_NONE;
2454 static char parse[20000];
2455 static int parse_pos = 0;
2456 static char buf[BUF_SIZE + 1];
2457 static int firstTime = TRUE, intfSet = FALSE;
2458 static ColorClass prevColor = ColorNormal;
2459 static int savingComment = FALSE;
2460 static int cmatch = 0; // continuation sequence match
2467 int backup; /* [DM] For zippy color lines */
2469 char talker[MSG_SIZ]; // [HGM] chat
2472 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2474 if (appData.debugMode) {
2476 fprintf(debugFP, "<ICS: ");
2477 show_bytes(debugFP, data, count);
2478 fprintf(debugFP, "\n");
2482 if (appData.debugMode) { int f = forwardMostMove;
2483 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2484 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2485 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2488 /* If last read ended with a partial line that we couldn't parse,
2489 prepend it to the new read and try again. */
2490 if (leftover_len > 0) {
2491 for (i=0; i<leftover_len; i++)
2492 buf[i] = buf[leftover_start + i];
2495 /* copy new characters into the buffer */
2496 bp = buf + leftover_len;
2497 buf_len=leftover_len;
2498 for (i=0; i<count; i++)
2501 if (data[i] == '\r')
2504 // join lines split by ICS?
2505 if (!appData.noJoin)
2508 Joining just consists of finding matches against the
2509 continuation sequence, and discarding that sequence
2510 if found instead of copying it. So, until a match
2511 fails, there's nothing to do since it might be the
2512 complete sequence, and thus, something we don't want
2515 if (data[i] == cont_seq[cmatch])
2518 if (cmatch == strlen(cont_seq))
2520 cmatch = 0; // complete match. just reset the counter
2523 it's possible for the ICS to not include the space
2524 at the end of the last word, making our [correct]
2525 join operation fuse two separate words. the server
2526 does this when the space occurs at the width setting.
2528 if (!buf_len || buf[buf_len-1] != ' ')
2539 match failed, so we have to copy what matched before
2540 falling through and copying this character. In reality,
2541 this will only ever be just the newline character, but
2542 it doesn't hurt to be precise.
2544 strncpy(bp, cont_seq, cmatch);
2556 buf[buf_len] = NULLCHAR;
2557 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2562 while (i < buf_len) {
2563 /* Deal with part of the TELNET option negotiation
2564 protocol. We refuse to do anything beyond the
2565 defaults, except that we allow the WILL ECHO option,
2566 which ICS uses to turn off password echoing when we are
2567 directly connected to it. We reject this option
2568 if localLineEditing mode is on (always on in xboard)
2569 and we are talking to port 23, which might be a real
2570 telnet server that will try to keep WILL ECHO on permanently.
2572 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2573 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2574 unsigned char option;
2576 switch ((unsigned char) buf[++i]) {
2578 if (appData.debugMode)
2579 fprintf(debugFP, "\n<WILL ");
2580 switch (option = (unsigned char) buf[++i]) {
2582 if (appData.debugMode)
2583 fprintf(debugFP, "ECHO ");
2584 /* Reply only if this is a change, according
2585 to the protocol rules. */
2586 if (remoteEchoOption) break;
2587 if (appData.localLineEditing &&
2588 atoi(appData.icsPort) == TN_PORT) {
2589 TelnetRequest(TN_DONT, TN_ECHO);
2592 TelnetRequest(TN_DO, TN_ECHO);
2593 remoteEchoOption = TRUE;
2597 if (appData.debugMode)
2598 fprintf(debugFP, "%d ", option);
2599 /* Whatever this is, we don't want it. */
2600 TelnetRequest(TN_DONT, option);
2605 if (appData.debugMode)
2606 fprintf(debugFP, "\n<WONT ");
2607 switch (option = (unsigned char) buf[++i]) {
2609 if (appData.debugMode)
2610 fprintf(debugFP, "ECHO ");
2611 /* Reply only if this is a change, according
2612 to the protocol rules. */
2613 if (!remoteEchoOption) break;
2615 TelnetRequest(TN_DONT, TN_ECHO);
2616 remoteEchoOption = FALSE;
2619 if (appData.debugMode)
2620 fprintf(debugFP, "%d ", (unsigned char) option);
2621 /* Whatever this is, it must already be turned
2622 off, because we never agree to turn on
2623 anything non-default, so according to the
2624 protocol rules, we don't reply. */
2629 if (appData.debugMode)
2630 fprintf(debugFP, "\n<DO ");
2631 switch (option = (unsigned char) buf[++i]) {
2633 /* Whatever this is, we refuse to do it. */
2634 if (appData.debugMode)
2635 fprintf(debugFP, "%d ", option);
2636 TelnetRequest(TN_WONT, option);
2641 if (appData.debugMode)
2642 fprintf(debugFP, "\n<DONT ");
2643 switch (option = (unsigned char) buf[++i]) {
2645 if (appData.debugMode)
2646 fprintf(debugFP, "%d ", option);
2647 /* Whatever this is, we are already not doing
2648 it, because we never agree to do anything
2649 non-default, so according to the protocol
2650 rules, we don't reply. */
2655 if (appData.debugMode)
2656 fprintf(debugFP, "\n<IAC ");
2657 /* Doubled IAC; pass it through */
2661 if (appData.debugMode)
2662 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2663 /* Drop all other telnet commands on the floor */
2666 if (oldi > next_out)
2667 SendToPlayer(&buf[next_out], oldi - next_out);
2673 /* OK, this at least will *usually* work */
2674 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2678 if (loggedOn && !intfSet) {
2679 if (ics_type == ICS_ICC) {
2680 snprintf(str, MSG_SIZ,
2681 "/set-quietly interface %s\n/set-quietly style 12\n",
2683 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2684 strcat(str, "/set-2 51 1\n/set seek 1\n");
2685 } else if (ics_type == ICS_CHESSNET) {
2686 snprintf(str, MSG_SIZ, "/style 12\n");
2688 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2689 strcat(str, programVersion);
2690 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2691 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2692 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2694 strcat(str, "$iset nohighlight 1\n");
2696 strcat(str, "$iset lock 1\n$style 12\n");
2699 NotifyFrontendLogin();
2703 if (started == STARTED_COMMENT) {
2704 /* Accumulate characters in comment */
2705 parse[parse_pos++] = buf[i];
2706 if (buf[i] == '\n') {
2707 parse[parse_pos] = NULLCHAR;
2708 if(chattingPartner>=0) {
2710 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2711 OutputChatMessage(chattingPartner, mess);
2712 chattingPartner = -1;
2713 next_out = i+1; // [HGM] suppress printing in ICS window
2715 if(!suppressKibitz) // [HGM] kibitz
2716 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2717 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2718 int nrDigit = 0, nrAlph = 0, j;
2719 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2720 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2721 parse[parse_pos] = NULLCHAR;
2722 // try to be smart: if it does not look like search info, it should go to
2723 // ICS interaction window after all, not to engine-output window.
2724 for(j=0; j<parse_pos; j++) { // count letters and digits
2725 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2726 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2727 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2729 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2730 int depth=0; float score;
2731 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2732 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2733 pvInfoList[forwardMostMove-1].depth = depth;
2734 pvInfoList[forwardMostMove-1].score = 100*score;
2736 OutputKibitz(suppressKibitz, parse);
2739 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2740 SendToPlayer(tmp, strlen(tmp));
2742 next_out = i+1; // [HGM] suppress printing in ICS window
2744 started = STARTED_NONE;
2746 /* Don't match patterns against characters in comment */
2751 if (started == STARTED_CHATTER) {
2752 if (buf[i] != '\n') {
2753 /* Don't match patterns against characters in chatter */
2757 started = STARTED_NONE;
2758 if(suppressKibitz) next_out = i+1;
2761 /* Kludge to deal with rcmd protocol */
2762 if (firstTime && looking_at(buf, &i, "\001*")) {
2763 DisplayFatalError(&buf[1], 0, 1);
2769 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2772 if (appData.debugMode)
2773 fprintf(debugFP, "ics_type %d\n", ics_type);
2776 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2777 ics_type = ICS_FICS;
2779 if (appData.debugMode)
2780 fprintf(debugFP, "ics_type %d\n", ics_type);
2783 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2784 ics_type = ICS_CHESSNET;
2786 if (appData.debugMode)
2787 fprintf(debugFP, "ics_type %d\n", ics_type);
2792 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2793 looking_at(buf, &i, "Logging you in as \"*\"") ||
2794 looking_at(buf, &i, "will be \"*\""))) {
2795 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2799 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2801 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2802 DisplayIcsInteractionTitle(buf);
2803 have_set_title = TRUE;
2806 /* skip finger notes */
2807 if (started == STARTED_NONE &&
2808 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2809 (buf[i] == '1' && buf[i+1] == '0')) &&
2810 buf[i+2] == ':' && buf[i+3] == ' ') {
2811 started = STARTED_CHATTER;
2817 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2818 if(appData.seekGraph) {
2819 if(soughtPending && MatchSoughtLine(buf+i)) {
2820 i = strstr(buf+i, "rated") - buf;
2821 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2822 next_out = leftover_start = i;
2823 started = STARTED_CHATTER;
2824 suppressKibitz = TRUE;
2827 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2828 && looking_at(buf, &i, "* ads displayed")) {
2829 soughtPending = FALSE;
2834 if(appData.autoRefresh) {
2835 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2836 int s = (ics_type == ICS_ICC); // ICC format differs
2838 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2839 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2840 looking_at(buf, &i, "*% "); // eat prompt
2841 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2842 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2843 next_out = i; // suppress
2846 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2847 char *p = star_match[0];
2849 if(seekGraphUp) RemoveSeekAd(atoi(p));
2850 while(*p && *p++ != ' '); // next
2852 looking_at(buf, &i, "*% "); // eat prompt
2853 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2860 /* skip formula vars */
2861 if (started == STARTED_NONE &&
2862 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2863 started = STARTED_CHATTER;
2868 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2869 if (appData.autoKibitz && started == STARTED_NONE &&
2870 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2871 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2872 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2873 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2874 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2875 suppressKibitz = TRUE;
2876 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2878 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2879 && (gameMode == IcsPlayingWhite)) ||
2880 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2881 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2882 started = STARTED_CHATTER; // own kibitz we simply discard
2884 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2885 parse_pos = 0; parse[0] = NULLCHAR;
2886 savingComment = TRUE;
2887 suppressKibitz = gameMode != IcsObserving ? 2 :
2888 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2892 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2893 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2894 && atoi(star_match[0])) {
2895 // suppress the acknowledgements of our own autoKibitz
2897 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2898 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2899 SendToPlayer(star_match[0], strlen(star_match[0]));
2900 if(looking_at(buf, &i, "*% ")) // eat prompt
2901 suppressKibitz = FALSE;
2905 } // [HGM] kibitz: end of patch
2907 // [HGM] chat: intercept tells by users for which we have an open chat window
2909 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2910 looking_at(buf, &i, "* whispers:") ||
2911 looking_at(buf, &i, "* kibitzes:") ||
2912 looking_at(buf, &i, "* shouts:") ||
2913 looking_at(buf, &i, "* c-shouts:") ||
2914 looking_at(buf, &i, "--> * ") ||
2915 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2916 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2917 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2918 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2920 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2921 chattingPartner = -1;
2923 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2924 for(p=0; p<MAX_CHAT; p++) {
2925 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2926 talker[0] = '['; strcat(talker, "] ");
2927 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2928 chattingPartner = p; break;
2931 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2932 for(p=0; p<MAX_CHAT; p++) {
2933 if(!strcmp("kibitzes", chatPartner[p])) {
2934 talker[0] = '['; strcat(talker, "] ");
2935 chattingPartner = p; break;
2938 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2939 for(p=0; p<MAX_CHAT; p++) {
2940 if(!strcmp("whispers", chatPartner[p])) {
2941 talker[0] = '['; strcat(talker, "] ");
2942 chattingPartner = p; break;
2945 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2946 if(buf[i-8] == '-' && buf[i-3] == 't')
2947 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2948 if(!strcmp("c-shouts", chatPartner[p])) {
2949 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2950 chattingPartner = p; break;
2953 if(chattingPartner < 0)
2954 for(p=0; p<MAX_CHAT; p++) {
2955 if(!strcmp("shouts", chatPartner[p])) {
2956 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2957 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2958 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2959 chattingPartner = p; break;
2963 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2964 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2965 talker[0] = 0; Colorize(ColorTell, FALSE);
2966 chattingPartner = p; break;
2968 if(chattingPartner<0) i = oldi; else {
2969 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2970 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2971 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2972 started = STARTED_COMMENT;
2973 parse_pos = 0; parse[0] = NULLCHAR;
2974 savingComment = 3 + chattingPartner; // counts as TRUE
2975 suppressKibitz = TRUE;
2978 } // [HGM] chat: end of patch
2980 if (appData.zippyTalk || appData.zippyPlay) {
2981 /* [DM] Backup address for color zippy lines */
2984 if (loggedOn == TRUE)
2985 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2986 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2988 } // [DM] 'else { ' deleted
2990 /* Regular tells and says */
2991 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2992 looking_at(buf, &i, "* (your partner) tells you: ") ||
2993 looking_at(buf, &i, "* says: ") ||
2994 /* Don't color "message" or "messages" output */
2995 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2996 looking_at(buf, &i, "*. * at *:*: ") ||
2997 looking_at(buf, &i, "--* (*:*): ") ||
2998 /* Message notifications (same color as tells) */
2999 looking_at(buf, &i, "* has left a message ") ||
3000 looking_at(buf, &i, "* just sent you a message:\n") ||
3001 /* Whispers and kibitzes */
3002 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3003 looking_at(buf, &i, "* kibitzes: ") ||
3005 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3007 if (tkind == 1 && strchr(star_match[0], ':')) {
3008 /* Avoid "tells you:" spoofs in channels */
3011 if (star_match[0][0] == NULLCHAR ||
3012 strchr(star_match[0], ' ') ||
3013 (tkind == 3 && strchr(star_match[1], ' '))) {
3014 /* Reject bogus matches */
3017 if (appData.colorize) {
3018 if (oldi > next_out) {
3019 SendToPlayer(&buf[next_out], oldi - next_out);
3024 Colorize(ColorTell, FALSE);
3025 curColor = ColorTell;
3028 Colorize(ColorKibitz, FALSE);
3029 curColor = ColorKibitz;
3032 p = strrchr(star_match[1], '(');
3039 Colorize(ColorChannel1, FALSE);
3040 curColor = ColorChannel1;
3042 Colorize(ColorChannel, FALSE);
3043 curColor = ColorChannel;
3047 curColor = ColorNormal;
3051 if (started == STARTED_NONE && appData.autoComment &&
3052 (gameMode == IcsObserving ||
3053 gameMode == IcsPlayingWhite ||
3054 gameMode == IcsPlayingBlack)) {
3055 parse_pos = i - oldi;
3056 memcpy(parse, &buf[oldi], parse_pos);
3057 parse[parse_pos] = NULLCHAR;
3058 started = STARTED_COMMENT;
3059 savingComment = TRUE;
3061 started = STARTED_CHATTER;
3062 savingComment = FALSE;
3069 if (looking_at(buf, &i, "* s-shouts: ") ||
3070 looking_at(buf, &i, "* c-shouts: ")) {
3071 if (appData.colorize) {
3072 if (oldi > next_out) {
3073 SendToPlayer(&buf[next_out], oldi - next_out);
3076 Colorize(ColorSShout, FALSE);
3077 curColor = ColorSShout;
3080 started = STARTED_CHATTER;
3084 if (looking_at(buf, &i, "--->")) {
3089 if (looking_at(buf, &i, "* shouts: ") ||
3090 looking_at(buf, &i, "--> ")) {
3091 if (appData.colorize) {
3092 if (oldi > next_out) {
3093 SendToPlayer(&buf[next_out], oldi - next_out);
3096 Colorize(ColorShout, FALSE);
3097 curColor = ColorShout;
3100 started = STARTED_CHATTER;
3104 if (looking_at( buf, &i, "Challenge:")) {
3105 if (appData.colorize) {
3106 if (oldi > next_out) {
3107 SendToPlayer(&buf[next_out], oldi - next_out);
3110 Colorize(ColorChallenge, FALSE);
3111 curColor = ColorChallenge;
3117 if (looking_at(buf, &i, "* offers you") ||
3118 looking_at(buf, &i, "* offers to be") ||
3119 looking_at(buf, &i, "* would like to") ||
3120 looking_at(buf, &i, "* requests to") ||
3121 looking_at(buf, &i, "Your opponent offers") ||
3122 looking_at(buf, &i, "Your opponent requests")) {
3124 if (appData.colorize) {
3125 if (oldi > next_out) {
3126 SendToPlayer(&buf[next_out], oldi - next_out);
3129 Colorize(ColorRequest, FALSE);
3130 curColor = ColorRequest;
3135 if (looking_at(buf, &i, "* (*) seeking")) {
3136 if (appData.colorize) {
3137 if (oldi > next_out) {
3138 SendToPlayer(&buf[next_out], oldi - next_out);
3141 Colorize(ColorSeek, FALSE);
3142 curColor = ColorSeek;
3147 if (looking_at(buf, &i, "\\ ")) {
3148 if (prevColor != ColorNormal) {
3149 if (oldi > next_out) {
3150 SendToPlayer(&buf[next_out], oldi - next_out);
3153 Colorize(prevColor, TRUE);
3154 curColor = prevColor;
3156 if (savingComment) {
3157 parse_pos = i - oldi;
3158 memcpy(parse, &buf[oldi], parse_pos);
3159 parse[parse_pos] = NULLCHAR;
3160 started = STARTED_COMMENT;
3161 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3162 chattingPartner = savingComment - 3; // kludge to remember the box
3164 started = STARTED_CHATTER;
3169 if (looking_at(buf, &i, "Black Strength :") ||
3170 looking_at(buf, &i, "<<< style 10 board >>>") ||
3171 looking_at(buf, &i, "<10>") ||
3172 looking_at(buf, &i, "#@#")) {
3173 /* Wrong board style */
3175 SendToICS(ics_prefix);
3176 SendToICS("set style 12\n");
3177 SendToICS(ics_prefix);
3178 SendToICS("refresh\n");
3182 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3184 have_sent_ICS_logon = 1;
3188 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3189 (looking_at(buf, &i, "\n<12> ") ||
3190 looking_at(buf, &i, "<12> "))) {
3192 if (oldi > next_out) {
3193 SendToPlayer(&buf[next_out], oldi - next_out);
3196 started = STARTED_BOARD;
3201 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3202 looking_at(buf, &i, "<b1> ")) {
3203 if (oldi > next_out) {
3204 SendToPlayer(&buf[next_out], oldi - next_out);
3207 started = STARTED_HOLDINGS;
3212 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3214 /* Header for a move list -- first line */
3216 switch (ics_getting_history) {
3220 case BeginningOfGame:
3221 /* User typed "moves" or "oldmoves" while we
3222 were idle. Pretend we asked for these
3223 moves and soak them up so user can step
3224 through them and/or save them.
3227 gameMode = IcsObserving;
3230 ics_getting_history = H_GOT_UNREQ_HEADER;
3232 case EditGame: /*?*/
3233 case EditPosition: /*?*/
3234 /* Should above feature work in these modes too? */
3235 /* For now it doesn't */
3236 ics_getting_history = H_GOT_UNWANTED_HEADER;
3239 ics_getting_history = H_GOT_UNWANTED_HEADER;
3244 /* Is this the right one? */
3245 if (gameInfo.white && gameInfo.black &&
3246 strcmp(gameInfo.white, star_match[0]) == 0 &&
3247 strcmp(gameInfo.black, star_match[2]) == 0) {
3249 ics_getting_history = H_GOT_REQ_HEADER;
3252 case H_GOT_REQ_HEADER:
3253 case H_GOT_UNREQ_HEADER:
3254 case H_GOT_UNWANTED_HEADER:
3255 case H_GETTING_MOVES:
3256 /* Should not happen */
3257 DisplayError(_("Error gathering move list: two headers"), 0);
3258 ics_getting_history = H_FALSE;
3262 /* Save player ratings into gameInfo if needed */
3263 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3264 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3265 (gameInfo.whiteRating == -1 ||
3266 gameInfo.blackRating == -1)) {
3268 gameInfo.whiteRating = string_to_rating(star_match[1]);
3269 gameInfo.blackRating = string_to_rating(star_match[3]);
3270 if (appData.debugMode)
3271 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3272 gameInfo.whiteRating, gameInfo.blackRating);
3277 if (looking_at(buf, &i,
3278 "* * match, initial time: * minute*, increment: * second")) {
3279 /* Header for a move list -- second line */
3280 /* Initial board will follow if this is a wild game */
3281 if (gameInfo.event != NULL) free(gameInfo.event);
3282 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3283 gameInfo.event = StrSave(str);
3284 /* [HGM] we switched variant. Translate boards if needed. */
3285 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3289 if (looking_at(buf, &i, "Move ")) {
3290 /* Beginning of a move list */
3291 switch (ics_getting_history) {
3293 /* Normally should not happen */
3294 /* Maybe user hit reset while we were parsing */
3297 /* Happens if we are ignoring a move list that is not
3298 * the one we just requested. Common if the user
3299 * tries to observe two games without turning off
3302 case H_GETTING_MOVES:
3303 /* Should not happen */
3304 DisplayError(_("Error gathering move list: nested"), 0);
3305 ics_getting_history = H_FALSE;
3307 case H_GOT_REQ_HEADER:
3308 ics_getting_history = H_GETTING_MOVES;
3309 started = STARTED_MOVES;
3311 if (oldi > next_out) {
3312 SendToPlayer(&buf[next_out], oldi - next_out);
3315 case H_GOT_UNREQ_HEADER:
3316 ics_getting_history = H_GETTING_MOVES;
3317 started = STARTED_MOVES_NOHIDE;
3320 case H_GOT_UNWANTED_HEADER:
3321 ics_getting_history = H_FALSE;
3327 if (looking_at(buf, &i, "% ") ||
3328 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3329 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3330 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3331 soughtPending = FALSE;
3335 if(suppressKibitz) next_out = i;
3336 savingComment = FALSE;
3340 case STARTED_MOVES_NOHIDE:
3341 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3342 parse[parse_pos + i - oldi] = NULLCHAR;
3343 ParseGameHistory(parse);
3345 if (appData.zippyPlay && first.initDone) {
3346 FeedMovesToProgram(&first, forwardMostMove);
3347 if (gameMode == IcsPlayingWhite) {
3348 if (WhiteOnMove(forwardMostMove)) {
3349 if (first.sendTime) {
3350 if (first.useColors) {
3351 SendToProgram("black\n", &first);
3353 SendTimeRemaining(&first, TRUE);
3355 if (first.useColors) {
3356 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3358 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3359 first.maybeThinking = TRUE;
3361 if (first.usePlayother) {
3362 if (first.sendTime) {
3363 SendTimeRemaining(&first, TRUE);
3365 SendToProgram("playother\n", &first);
3371 } else if (gameMode == IcsPlayingBlack) {
3372 if (!WhiteOnMove(forwardMostMove)) {
3373 if (first.sendTime) {
3374 if (first.useColors) {
3375 SendToProgram("white\n", &first);
3377 SendTimeRemaining(&first, FALSE);
3379 if (first.useColors) {
3380 SendToProgram("black\n", &first);
3382 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3383 first.maybeThinking = TRUE;
3385 if (first.usePlayother) {
3386 if (first.sendTime) {
3387 SendTimeRemaining(&first, FALSE);
3389 SendToProgram("playother\n", &first);
3398 if (gameMode == IcsObserving && ics_gamenum == -1) {
3399 /* Moves came from oldmoves or moves command
3400 while we weren't doing anything else.
3402 currentMove = forwardMostMove;
3403 ClearHighlights();/*!!could figure this out*/
3404 flipView = appData.flipView;
3405 DrawPosition(TRUE, boards[currentMove]);
3406 DisplayBothClocks();
3407 snprintf(str, MSG_SIZ, "%s vs. %s",
3408 gameInfo.white, gameInfo.black);
3412 /* Moves were history of an active game */
3413 if (gameInfo.resultDetails != NULL) {
3414 free(gameInfo.resultDetails);
3415 gameInfo.resultDetails = NULL;
3418 HistorySet(parseList, backwardMostMove,
3419 forwardMostMove, currentMove-1);
3420 DisplayMove(currentMove - 1);
3421 if (started == STARTED_MOVES) next_out = i;
3422 started = STARTED_NONE;
3423 ics_getting_history = H_FALSE;
3426 case STARTED_OBSERVE:
3427 started = STARTED_NONE;
3428 SendToICS(ics_prefix);
3429 SendToICS("refresh\n");
3435 if(bookHit) { // [HGM] book: simulate book reply
3436 static char bookMove[MSG_SIZ]; // a bit generous?
3438 programStats.nodes = programStats.depth = programStats.time =
3439 programStats.score = programStats.got_only_move = 0;
3440 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3442 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3443 strcat(bookMove, bookHit);
3444 HandleMachineMove(bookMove, &first);
3449 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3450 started == STARTED_HOLDINGS ||
3451 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3452 /* Accumulate characters in move list or board */
3453 parse[parse_pos++] = buf[i];
3456 /* Start of game messages. Mostly we detect start of game
3457 when the first board image arrives. On some versions
3458 of the ICS, though, we need to do a "refresh" after starting
3459 to observe in order to get the current board right away. */
3460 if (looking_at(buf, &i, "Adding game * to observation list")) {
3461 started = STARTED_OBSERVE;
3465 /* Handle auto-observe */
3466 if (appData.autoObserve &&
3467 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3468 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3470 /* Choose the player that was highlighted, if any. */
3471 if (star_match[0][0] == '\033' ||
3472 star_match[1][0] != '\033') {
3473 player = star_match[0];
3475 player = star_match[2];
3477 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3478 ics_prefix, StripHighlightAndTitle(player));
3481 /* Save ratings from notify string */
3482 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3483 player1Rating = string_to_rating(star_match[1]);
3484 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3485 player2Rating = string_to_rating(star_match[3]);
3487 if (appData.debugMode)
3489 "Ratings from 'Game notification:' %s %d, %s %d\n",
3490 player1Name, player1Rating,
3491 player2Name, player2Rating);
3496 /* Deal with automatic examine mode after a game,
3497 and with IcsObserving -> IcsExamining transition */
3498 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3499 looking_at(buf, &i, "has made you an examiner of game *")) {
3501 int gamenum = atoi(star_match[0]);
3502 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3503 gamenum == ics_gamenum) {
3504 /* We were already playing or observing this game;
3505 no need to refetch history */
3506 gameMode = IcsExamining;
3508 pauseExamForwardMostMove = forwardMostMove;
3509 } else if (currentMove < forwardMostMove) {
3510 ForwardInner(forwardMostMove);
3513 /* I don't think this case really can happen */
3514 SendToICS(ics_prefix);
3515 SendToICS("refresh\n");
3520 /* Error messages */
3521 // if (ics_user_moved) {
3522 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3523 if (looking_at(buf, &i, "Illegal move") ||
3524 looking_at(buf, &i, "Not a legal move") ||
3525 looking_at(buf, &i, "Your king is in check") ||
3526 looking_at(buf, &i, "It isn't your turn") ||
3527 looking_at(buf, &i, "It is not your move")) {
3529 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3530 currentMove = forwardMostMove-1;
3531 DisplayMove(currentMove - 1); /* before DMError */
3532 DrawPosition(FALSE, boards[currentMove]);
3533 SwitchClocks(forwardMostMove-1); // [HGM] race
3534 DisplayBothClocks();
3536 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3542 if (looking_at(buf, &i, "still have time") ||
3543 looking_at(buf, &i, "not out of time") ||
3544 looking_at(buf, &i, "either player is out of time") ||
3545 looking_at(buf, &i, "has timeseal; checking")) {
3546 /* We must have called his flag a little too soon */
3547 whiteFlag = blackFlag = FALSE;
3551 if (looking_at(buf, &i, "added * seconds to") ||
3552 looking_at(buf, &i, "seconds were added to")) {
3553 /* Update the clocks */
3554 SendToICS(ics_prefix);
3555 SendToICS("refresh\n");
3559 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3560 ics_clock_paused = TRUE;
3565 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3566 ics_clock_paused = FALSE;
3571 /* Grab player ratings from the Creating: message.
3572 Note we have to check for the special case when
3573 the ICS inserts things like [white] or [black]. */
3574 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3575 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3577 0 player 1 name (not necessarily white)
3579 2 empty, white, or black (IGNORED)
3580 3 player 2 name (not necessarily black)
3583 The names/ratings are sorted out when the game
3584 actually starts (below).
3586 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3587 player1Rating = string_to_rating(star_match[1]);
3588 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3589 player2Rating = string_to_rating(star_match[4]);
3591 if (appData.debugMode)
3593 "Ratings from 'Creating:' %s %d, %s %d\n",
3594 player1Name, player1Rating,
3595 player2Name, player2Rating);
3600 /* Improved generic start/end-of-game messages */
3601 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3602 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3603 /* If tkind == 0: */
3604 /* star_match[0] is the game number */
3605 /* [1] is the white player's name */
3606 /* [2] is the black player's name */
3607 /* For end-of-game: */
3608 /* [3] is the reason for the game end */
3609 /* [4] is a PGN end game-token, preceded by " " */
3610 /* For start-of-game: */
3611 /* [3] begins with "Creating" or "Continuing" */
3612 /* [4] is " *" or empty (don't care). */
3613 int gamenum = atoi(star_match[0]);
3614 char *whitename, *blackname, *why, *endtoken;
3615 ChessMove endtype = EndOfFile;
3618 whitename = star_match[1];
3619 blackname = star_match[2];
3620 why = star_match[3];
3621 endtoken = star_match[4];
3623 whitename = star_match[1];
3624 blackname = star_match[3];
3625 why = star_match[5];
3626 endtoken = star_match[6];
3629 /* Game start messages */
3630 if (strncmp(why, "Creating ", 9) == 0 ||
3631 strncmp(why, "Continuing ", 11) == 0) {
3632 gs_gamenum = gamenum;
3633 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3634 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3636 if (appData.zippyPlay) {
3637 ZippyGameStart(whitename, blackname);
3640 partnerBoardValid = FALSE; // [HGM] bughouse
3644 /* Game end messages */
3645 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3646 ics_gamenum != gamenum) {
3649 while (endtoken[0] == ' ') endtoken++;
3650 switch (endtoken[0]) {
3653 endtype = GameUnfinished;
3656 endtype = BlackWins;
3659 if (endtoken[1] == '/')
3660 endtype = GameIsDrawn;
3662 endtype = WhiteWins;
3665 GameEnds(endtype, why, GE_ICS);
3667 if (appData.zippyPlay && first.initDone) {
3668 ZippyGameEnd(endtype, why);
3669 if (first.pr == NULL) {
3670 /* Start the next process early so that we'll
3671 be ready for the next challenge */
3672 StartChessProgram(&first);
3674 /* Send "new" early, in case this command takes
3675 a long time to finish, so that we'll be ready
3676 for the next challenge. */
3677 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3681 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3685 if (looking_at(buf, &i, "Removing game * from observation") ||
3686 looking_at(buf, &i, "no longer observing game *") ||
3687 looking_at(buf, &i, "Game * (*) has no examiners")) {
3688 if (gameMode == IcsObserving &&
3689 atoi(star_match[0]) == ics_gamenum)
3691 /* icsEngineAnalyze */
3692 if (appData.icsEngineAnalyze) {
3699 ics_user_moved = FALSE;
3704 if (looking_at(buf, &i, "no longer examining game *")) {
3705 if (gameMode == IcsExamining &&
3706 atoi(star_match[0]) == ics_gamenum)
3710 ics_user_moved = FALSE;
3715 /* Advance leftover_start past any newlines we find,
3716 so only partial lines can get reparsed */
3717 if (looking_at(buf, &i, "\n")) {
3718 prevColor = curColor;
3719 if (curColor != ColorNormal) {
3720 if (oldi > next_out) {
3721 SendToPlayer(&buf[next_out], oldi - next_out);
3724 Colorize(ColorNormal, FALSE);
3725 curColor = ColorNormal;
3727 if (started == STARTED_BOARD) {
3728 started = STARTED_NONE;
3729 parse[parse_pos] = NULLCHAR;
3730 ParseBoard12(parse);
3733 /* Send premove here */
3734 if (appData.premove) {
3736 if (currentMove == 0 &&
3737 gameMode == IcsPlayingWhite &&
3738 appData.premoveWhite) {
3739 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3740 if (appData.debugMode)
3741 fprintf(debugFP, "Sending premove:\n");
3743 } else if (currentMove == 1 &&
3744 gameMode == IcsPlayingBlack &&
3745 appData.premoveBlack) {
3746 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3747 if (appData.debugMode)
3748 fprintf(debugFP, "Sending premove:\n");
3750 } else if (gotPremove) {
3752 ClearPremoveHighlights();
3753 if (appData.debugMode)
3754 fprintf(debugFP, "Sending premove:\n");
3755 UserMoveEvent(premoveFromX, premoveFromY,
3756 premoveToX, premoveToY,
3761 /* Usually suppress following prompt */
3762 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3763 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3764 if (looking_at(buf, &i, "*% ")) {
3765 savingComment = FALSE;
3770 } else if (started == STARTED_HOLDINGS) {
3772 char new_piece[MSG_SIZ];
3773 started = STARTED_NONE;
3774 parse[parse_pos] = NULLCHAR;
3775 if (appData.debugMode)
3776 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3777 parse, currentMove);
3778 if (sscanf(parse, " game %d", &gamenum) == 1) {
3779 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3780 if (gameInfo.variant == VariantNormal) {
3781 /* [HGM] We seem to switch variant during a game!
3782 * Presumably no holdings were displayed, so we have
3783 * to move the position two files to the right to
3784 * create room for them!
3786 VariantClass newVariant;
3787 switch(gameInfo.boardWidth) { // base guess on board width
3788 case 9: newVariant = VariantShogi; break;
3789 case 10: newVariant = VariantGreat; break;
3790 default: newVariant = VariantCrazyhouse; break;
3792 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3793 /* Get a move list just to see the header, which
3794 will tell us whether this is really bug or zh */
3795 if (ics_getting_history == H_FALSE) {
3796 ics_getting_history = H_REQUESTED;
3797 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3801 new_piece[0] = NULLCHAR;
3802 sscanf(parse, "game %d white [%s black [%s <- %s",
3803 &gamenum, white_holding, black_holding,
3805 white_holding[strlen(white_holding)-1] = NULLCHAR;
3806 black_holding[strlen(black_holding)-1] = NULLCHAR;
3807 /* [HGM] copy holdings to board holdings area */
3808 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3809 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3810 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3812 if (appData.zippyPlay && first.initDone) {
3813 ZippyHoldings(white_holding, black_holding,
3817 if (tinyLayout || smallLayout) {
3818 char wh[16], bh[16];
3819 PackHolding(wh, white_holding);
3820 PackHolding(bh, black_holding);
3821 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,