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, 2012, 2013, 2014 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 int flock(int f, int code);
63 # define EGBB_NAME "egbbdll64.dll"
65 # define EGBB_NAME "egbbdll.dll"
70 # include <sys/file.h>
75 # define EGBB_NAME "egbbso64.so"
77 # define EGBB_NAME "egbbso.so"
79 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
81 # define HMODULE void *
82 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 # define GetProcAddress dlsym
93 #include <sys/types.h>
102 #else /* not STDC_HEADERS */
105 # else /* not HAVE_STRING_H */
106 # include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
123 # include <sys/time.h>
129 #if defined(_amigados) && !defined(__GNUC__)
134 extern int gettimeofday(struct timeval *, struct timezone *);
142 #include "frontend.h"
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172 char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174 char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187 /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
272 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
298 /* States for ics_getting_history */
300 #define H_REQUESTED 1
301 #define H_GOT_REQ_HEADER 2
302 #define H_GOT_UNREQ_HEADER 3
303 #define H_GETTING_MOVES 4
304 #define H_GOT_UNWANTED_HEADER 5
306 /* whosays values for GameEnds */
315 /* Maximum number of games in a cmail message */
316 #define CMAIL_MAX_GAMES 20
318 /* Different types of move when calling RegisterMove */
320 #define CMAIL_RESIGN 1
322 #define CMAIL_ACCEPT 3
324 /* Different types of result to remember for each game */
325 #define CMAIL_NOT_RESULT 0
326 #define CMAIL_OLD_RESULT 1
327 #define CMAIL_NEW_RESULT 2
329 /* Telnet protocol constants */
340 safeStrCpy (char *dst, const char *src, size_t count)
343 assert( dst != NULL );
344 assert( src != NULL );
347 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
348 if( i == count && dst[count-1] != NULLCHAR)
350 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
351 if(appData.debugMode)
352 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358 /* Some compiler can't cast u64 to double
359 * This function do the job for us:
361 * We use the highest bit for cast, this only
362 * works if the highest bit is not
363 * in use (This should not happen)
365 * We used this for all compiler
368 u64ToDouble (u64 value)
371 u64 tmp = value & u64Const(0x7fffffffffffffff);
372 r = (double)(s64)tmp;
373 if (value & u64Const(0x8000000000000000))
374 r += 9.2233720368547758080e18; /* 2^63 */
378 /* Fake up flags for now, as we aren't keeping track of castling
379 availability yet. [HGM] Change of logic: the flag now only
380 indicates the type of castlings allowed by the rule of the game.
381 The actual rights themselves are maintained in the array
382 castlingRights, as part of the game history, and are not probed
388 int flags = F_ALL_CASTLE_OK;
389 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
390 switch (gameInfo.variant) {
392 flags &= ~F_ALL_CASTLE_OK;
393 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
394 flags |= F_IGNORE_CHECK;
396 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401 case VariantKriegspiel:
402 flags |= F_KRIEGSPIEL_CAPTURE;
404 case VariantCapaRandom:
405 case VariantFischeRandom:
406 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
407 case VariantNoCastle:
408 case VariantShatranj:
413 flags &= ~F_ALL_CASTLE_OK;
421 FILE *gameFileFP, *debugFP, *serverFP;
422 char *currentDebugFile; // [HGM] debug split: to remember name
425 [AS] Note: sometimes, the sscanf() function is used to parse the input
426 into a fixed-size buffer. Because of this, we must be prepared to
427 receive strings as long as the size of the input buffer, which is currently
428 set to 4K for Windows and 8K for the rest.
429 So, we must either allocate sufficiently large buffers here, or
430 reduce the size of the input buffer in the input reading part.
433 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
434 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
435 char thinkOutput1[MSG_SIZ*10];
437 ChessProgramState first, second, pairing;
439 /* premove variables */
442 int premoveFromX = 0;
443 int premoveFromY = 0;
444 int premovePromoChar = 0;
446 Boolean alarmSounded;
447 /* end premove variables */
449 char *ics_prefix = "$";
450 enum ICS_TYPE ics_type = ICS_GENERIC;
452 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
453 int pauseExamForwardMostMove = 0;
454 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
455 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
456 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
457 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
458 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
459 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
460 int whiteFlag = FALSE, blackFlag = FALSE;
461 int userOfferedDraw = FALSE;
462 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
463 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
464 int cmailMoveType[CMAIL_MAX_GAMES];
465 long ics_clock_paused = 0;
466 ProcRef icsPR = NoProc, cmailPR = NoProc;
467 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
468 GameMode gameMode = BeginningOfGame;
469 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
470 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
471 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
472 int hiddenThinkOutputState = 0; /* [AS] */
473 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
474 int adjudicateLossPlies = 6;
475 char white_holding[64], black_holding[64];
476 TimeMark lastNodeCountTime;
477 long lastNodeCount=0;
478 int shiftKey, controlKey; // [HGM] set by mouse handler
480 int have_sent_ICS_logon = 0;
482 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
483 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
484 Boolean adjustedClock;
485 long timeControl_2; /* [AS] Allow separate time controls */
486 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
487 long timeRemaining[2][MAX_MOVES];
488 int matchGame = 0, nextGame = 0, roundNr = 0;
489 Boolean waitingForGame = FALSE, startingEngine = FALSE;
490 TimeMark programStartTime, pauseStart;
491 char ics_handle[MSG_SIZ];
492 int have_set_title = 0;
494 /* animateTraining preserves the state of appData.animate
495 * when Training mode is activated. This allows the
496 * response to be animated when appData.animate == TRUE and
497 * appData.animateDragging == TRUE.
499 Boolean animateTraining;
505 Board boards[MAX_MOVES];
506 /* [HGM] Following 7 needed for accurate legality tests: */
507 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
508 signed char initialRights[BOARD_FILES];
509 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
510 int initialRulePlies, FENrulePlies;
511 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
513 Boolean shuffleOpenings;
514 int mute; // mute all sounds
516 // [HGM] vari: next 12 to save and restore variations
517 #define MAX_VARIATIONS 10
518 int framePtr = MAX_MOVES-1; // points to free stack entry
520 int savedFirst[MAX_VARIATIONS];
521 int savedLast[MAX_VARIATIONS];
522 int savedFramePtr[MAX_VARIATIONS];
523 char *savedDetails[MAX_VARIATIONS];
524 ChessMove savedResult[MAX_VARIATIONS];
526 void PushTail P((int firstMove, int lastMove));
527 Boolean PopTail P((Boolean annotate));
528 void PushInner P((int firstMove, int lastMove));
529 void PopInner P((Boolean annotate));
530 void CleanupTail P((void));
532 ChessSquare FIDEArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackKnight, BlackRook }
539 ChessSquare twoKingsArray[2][BOARD_FILES] = {
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
542 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
543 BlackKing, BlackKing, BlackKnight, BlackRook }
546 ChessSquare KnightmateArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
548 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
549 { BlackRook, BlackMan, BlackBishop, BlackQueen,
550 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
553 ChessSquare SpartanArray[2][BOARD_FILES] = {
554 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
555 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
556 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
557 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
560 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
561 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
562 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
563 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
564 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
567 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
568 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
569 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
570 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
571 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
575 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
576 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
577 { BlackRook, BlackKnight, BlackMan, BlackFerz,
578 BlackKing, BlackMan, BlackKnight, BlackRook }
581 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
582 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
583 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
584 { BlackRook, BlackKnight, BlackMan, BlackFerz,
585 BlackKing, BlackMan, BlackKnight, BlackRook }
588 ChessSquare lionArray[2][BOARD_FILES] = {
589 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
590 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
591 { BlackRook, BlackLion, BlackBishop, BlackQueen,
592 BlackKing, BlackBishop, BlackKnight, BlackRook }
596 #if (BOARD_FILES>=10)
597 ChessSquare ShogiArray[2][BOARD_FILES] = {
598 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
599 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
600 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
601 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
604 ChessSquare XiangqiArray[2][BOARD_FILES] = {
605 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
606 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
607 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
608 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
611 ChessSquare CapablancaArray[2][BOARD_FILES] = {
612 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
613 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
614 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
615 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
618 ChessSquare GreatArray[2][BOARD_FILES] = {
619 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
620 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
621 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
622 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
625 ChessSquare JanusArray[2][BOARD_FILES] = {
626 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
627 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
628 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
629 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
632 ChessSquare GrandArray[2][BOARD_FILES] = {
633 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
634 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
635 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
636 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
639 ChessSquare ChuChessArray[2][BOARD_FILES] = {
640 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
641 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
642 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
643 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
647 ChessSquare GothicArray[2][BOARD_FILES] = {
648 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
649 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
650 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
651 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
654 #define GothicArray CapablancaArray
658 ChessSquare FalconArray[2][BOARD_FILES] = {
659 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
660 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
661 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
662 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
665 #define FalconArray CapablancaArray
668 #else // !(BOARD_FILES>=10)
669 #define XiangqiPosition FIDEArray
670 #define CapablancaArray FIDEArray
671 #define GothicArray FIDEArray
672 #define GreatArray FIDEArray
673 #endif // !(BOARD_FILES>=10)
675 #if (BOARD_FILES>=12)
676 ChessSquare CourierArray[2][BOARD_FILES] = {
677 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
678 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
679 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
680 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
682 ChessSquare ChuArray[6][BOARD_FILES] = {
683 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
684 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
685 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
686 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
687 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
688 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
689 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
690 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
691 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
692 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
693 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
694 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
696 #else // !(BOARD_FILES>=12)
697 #define CourierArray CapablancaArray
698 #define ChuArray CapablancaArray
699 #endif // !(BOARD_FILES>=12)
702 Board initialPosition;
705 /* Convert str to a rating. Checks for special cases of "----",
707 "++++", etc. Also strips ()'s */
709 string_to_rating (char *str)
711 while(*str && !isdigit(*str)) ++str;
713 return 0; /* One of the special "no rating" cases */
721 /* Init programStats */
722 programStats.movelist[0] = 0;
723 programStats.depth = 0;
724 programStats.nr_moves = 0;
725 programStats.moves_left = 0;
726 programStats.nodes = 0;
727 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
728 programStats.score = 0;
729 programStats.got_only_move = 0;
730 programStats.got_fail = 0;
731 programStats.line_is_book = 0;
736 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
737 if (appData.firstPlaysBlack) {
738 first.twoMachinesColor = "black\n";
739 second.twoMachinesColor = "white\n";
741 first.twoMachinesColor = "white\n";
742 second.twoMachinesColor = "black\n";
745 first.other = &second;
746 second.other = &first;
749 if(appData.timeOddsMode) {
750 norm = appData.timeOdds[0];
751 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
753 first.timeOdds = appData.timeOdds[0]/norm;
754 second.timeOdds = appData.timeOdds[1]/norm;
757 if(programVersion) free(programVersion);
758 if (appData.noChessProgram) {
759 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
760 sprintf(programVersion, "%s", PACKAGE_STRING);
762 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
763 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
764 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
769 UnloadEngine (ChessProgramState *cps)
771 /* Kill off first chess program */
772 if (cps->isr != NULL)
773 RemoveInputSource(cps->isr);
776 if (cps->pr != NoProc) {
778 DoSleep( appData.delayBeforeQuit );
779 SendToProgram("quit\n", cps);
780 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
783 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
787 ClearOptions (ChessProgramState *cps)
790 cps->nrOptions = cps->comboCnt = 0;
791 for(i=0; i<MAX_OPTIONS; i++) {
792 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
793 cps->option[i].textValue = 0;
797 char *engineNames[] = {
798 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
799 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
801 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
802 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
807 InitEngine (ChessProgramState *cps, int n)
808 { // [HGM] all engine initialiation put in a function that does one engine
812 cps->which = engineNames[n];
813 cps->maybeThinking = FALSE;
817 cps->sendDrawOffers = 1;
819 cps->program = appData.chessProgram[n];
820 cps->host = appData.host[n];
821 cps->dir = appData.directory[n];
822 cps->initString = appData.engInitString[n];
823 cps->computerString = appData.computerString[n];
824 cps->useSigint = TRUE;
825 cps->useSigterm = TRUE;
826 cps->reuse = appData.reuse[n];
827 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
828 cps->useSetboard = FALSE;
830 cps->usePing = FALSE;
833 cps->usePlayother = FALSE;
834 cps->useColors = TRUE;
835 cps->useUsermove = FALSE;
836 cps->sendICS = FALSE;
837 cps->sendName = appData.icsActive;
838 cps->sdKludge = FALSE;
839 cps->stKludge = FALSE;
840 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
841 TidyProgramName(cps->program, cps->host, cps->tidy);
843 ASSIGN(cps->variants, appData.variant);
844 cps->analysisSupport = 2; /* detect */
845 cps->analyzing = FALSE;
846 cps->initDone = FALSE;
849 /* New features added by Tord: */
850 cps->useFEN960 = FALSE;
851 cps->useOOCastle = TRUE;
852 /* End of new features added by Tord. */
853 cps->fenOverride = appData.fenOverride[n];
855 /* [HGM] time odds: set factor for each machine */
856 cps->timeOdds = appData.timeOdds[n];
858 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
859 cps->accumulateTC = appData.accumulateTC[n];
860 cps->maxNrOfSessions = 1;
865 cps->drawDepth = appData.drawDepth[n];
866 cps->supportsNPS = UNKNOWN;
867 cps->memSize = FALSE;
868 cps->maxCores = FALSE;
869 ASSIGN(cps->egtFormats, "");
872 cps->optionSettings = appData.engOptions[n];
874 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
875 cps->isUCI = appData.isUCI[n]; /* [AS] */
876 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
879 if (appData.protocolVersion[n] > PROTOVER
880 || appData.protocolVersion[n] < 1)
885 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
886 appData.protocolVersion[n]);
887 if( (len >= MSG_SIZ) && appData.debugMode )
888 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
890 DisplayFatalError(buf, 0, 2);
894 cps->protocolVersion = appData.protocolVersion[n];
897 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
898 ParseFeatures(appData.featureDefaults, cps);
901 ChessProgramState *savCps;
909 if(WaitForEngine(savCps, LoadEngine)) return;
910 CommonEngineInit(); // recalculate time odds
911 if(gameInfo.variant != StringToVariant(appData.variant)) {
912 // we changed variant when loading the engine; this forces us to reset
913 Reset(TRUE, savCps != &first);
914 oldMode = BeginningOfGame; // to prevent restoring old mode
916 InitChessProgram(savCps, FALSE);
917 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
918 DisplayMessage("", "");
919 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
920 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
923 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
927 ReplaceEngine (ChessProgramState *cps, int n)
929 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
931 if(oldMode != BeginningOfGame) EditGameEvent();
934 appData.noChessProgram = FALSE;
935 appData.clockMode = TRUE;
938 if(n) return; // only startup first engine immediately; second can wait
939 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
943 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
944 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
946 static char resetOptions[] =
947 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
948 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
949 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
950 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
953 FloatToFront(char **list, char *engineLine)
955 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
957 if(appData.recentEngines <= 0) return;
958 TidyProgramName(engineLine, "localhost", tidy+1);
959 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
960 strncpy(buf+1, *list, MSG_SIZ-50);
961 if(p = strstr(buf, tidy)) { // tidy name appears in list
962 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
963 while(*p++ = *++q); // squeeze out
965 strcat(tidy, buf+1); // put list behind tidy name
966 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
967 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
968 ASSIGN(*list, tidy+1);
971 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
974 Load (ChessProgramState *cps, int i)
976 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
977 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
978 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
979 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
980 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
981 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
982 appData.firstProtocolVersion = PROTOVER;
983 ParseArgsFromString(buf);
985 ReplaceEngine(cps, i);
986 FloatToFront(&appData.recentEngineList, engineLine);
990 while(q = strchr(p, SLASH)) p = q+1;
991 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
992 if(engineDir[0] != NULLCHAR) {
993 ASSIGN(appData.directory[i], engineDir); p = engineName;
994 } else if(p != engineName) { // derive directory from engine path, when not given
996 ASSIGN(appData.directory[i], engineName);
998 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
999 } else { ASSIGN(appData.directory[i], "."); }
1000 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1002 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1003 snprintf(command, MSG_SIZ, "%s %s", p, params);
1006 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1007 ASSIGN(appData.chessProgram[i], p);
1008 appData.isUCI[i] = isUCI;
1009 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1010 appData.hasOwnBookUCI[i] = hasBook;
1011 if(!nickName[0]) useNick = FALSE;
1012 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1016 q = firstChessProgramNames;
1017 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1018 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1019 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1020 quote, p, quote, appData.directory[i],
1021 useNick ? " -fn \"" : "",
1022 useNick ? nickName : "",
1023 useNick ? "\"" : "",
1024 v1 ? " -firstProtocolVersion 1" : "",
1025 hasBook ? "" : " -fNoOwnBookUCI",
1026 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1027 storeVariant ? " -variant " : "",
1028 storeVariant ? VariantName(gameInfo.variant) : "");
1029 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1030 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1031 if(insert != q) insert[-1] = NULLCHAR;
1032 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1034 FloatToFront(&appData.recentEngineList, buf);
1036 ReplaceEngine(cps, i);
1042 int matched, min, sec;
1044 * Parse timeControl resource
1046 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1047 appData.movesPerSession)) {
1049 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1050 DisplayFatalError(buf, 0, 2);
1054 * Parse searchTime resource
1056 if (*appData.searchTime != NULLCHAR) {
1057 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1059 searchTime = min * 60;
1060 } else if (matched == 2) {
1061 searchTime = min * 60 + sec;
1064 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1065 DisplayFatalError(buf, 0, 2);
1074 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1075 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1077 GetTimeMark(&programStartTime);
1078 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1079 appData.seedBase = random() + (random()<<15);
1080 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1082 ClearProgramStats();
1083 programStats.ok_to_send = 1;
1084 programStats.seen_stat = 0;
1087 * Initialize game list
1093 * Internet chess server status
1095 if (appData.icsActive) {
1096 appData.matchMode = FALSE;
1097 appData.matchGames = 0;
1099 appData.noChessProgram = !appData.zippyPlay;
1101 appData.zippyPlay = FALSE;
1102 appData.zippyTalk = FALSE;
1103 appData.noChessProgram = TRUE;
1105 if (*appData.icsHelper != NULLCHAR) {
1106 appData.useTelnet = TRUE;
1107 appData.telnetProgram = appData.icsHelper;
1110 appData.zippyTalk = appData.zippyPlay = FALSE;
1113 /* [AS] Initialize pv info list [HGM] and game state */
1117 for( i=0; i<=framePtr; i++ ) {
1118 pvInfoList[i].depth = -1;
1119 boards[i][EP_STATUS] = EP_NONE;
1120 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1126 /* [AS] Adjudication threshold */
1127 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1129 InitEngine(&first, 0);
1130 InitEngine(&second, 1);
1133 pairing.which = "pairing"; // pairing engine
1134 pairing.pr = NoProc;
1136 pairing.program = appData.pairingEngine;
1137 pairing.host = "localhost";
1140 if (appData.icsActive) {
1141 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1142 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1143 appData.clockMode = FALSE;
1144 first.sendTime = second.sendTime = 0;
1148 /* Override some settings from environment variables, for backward
1149 compatibility. Unfortunately it's not feasible to have the env
1150 vars just set defaults, at least in xboard. Ugh.
1152 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1157 if (!appData.icsActive) {
1161 /* Check for variants that are supported only in ICS mode,
1162 or not at all. Some that are accepted here nevertheless
1163 have bugs; see comments below.
1165 VariantClass variant = StringToVariant(appData.variant);
1167 case VariantBughouse: /* need four players and two boards */
1168 case VariantKriegspiel: /* need to hide pieces and move details */
1169 /* case VariantFischeRandom: (Fabien: moved below) */
1170 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1171 if( (len >= MSG_SIZ) && appData.debugMode )
1172 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1174 DisplayFatalError(buf, 0, 2);
1177 case VariantUnknown:
1178 case VariantLoadable:
1188 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1189 if( (len >= MSG_SIZ) && appData.debugMode )
1190 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1192 DisplayFatalError(buf, 0, 2);
1195 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1196 case VariantFairy: /* [HGM] TestLegality definitely off! */
1197 case VariantGothic: /* [HGM] should work */
1198 case VariantCapablanca: /* [HGM] should work */
1199 case VariantCourier: /* [HGM] initial forced moves not implemented */
1200 case VariantShogi: /* [HGM] could still mate with pawn drop */
1201 case VariantChu: /* [HGM] experimental */
1202 case VariantKnightmate: /* [HGM] should work */
1203 case VariantCylinder: /* [HGM] untested */
1204 case VariantFalcon: /* [HGM] untested */
1205 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1206 offboard interposition not understood */
1207 case VariantNormal: /* definitely works! */
1208 case VariantWildCastle: /* pieces not automatically shuffled */
1209 case VariantNoCastle: /* pieces not automatically shuffled */
1210 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1211 case VariantLosers: /* should work except for win condition,
1212 and doesn't know captures are mandatory */
1213 case VariantSuicide: /* should work except for win condition,
1214 and doesn't know captures are mandatory */
1215 case VariantGiveaway: /* should work except for win condition,
1216 and doesn't know captures are mandatory */
1217 case VariantTwoKings: /* should work */
1218 case VariantAtomic: /* should work except for win condition */
1219 case Variant3Check: /* should work except for win condition */
1220 case VariantShatranj: /* should work except for all win conditions */
1221 case VariantMakruk: /* should work except for draw countdown */
1222 case VariantASEAN : /* should work except for draw countdown */
1223 case VariantBerolina: /* might work if TestLegality is off */
1224 case VariantCapaRandom: /* should work */
1225 case VariantJanus: /* should work */
1226 case VariantSuper: /* experimental */
1227 case VariantGreat: /* experimental, requires legality testing to be off */
1228 case VariantSChess: /* S-Chess, should work */
1229 case VariantGrand: /* should work */
1230 case VariantSpartan: /* should work */
1231 case VariantLion: /* should work */
1232 case VariantChuChess: /* should work */
1240 NextIntegerFromString (char ** str, long * value)
1245 while( *s == ' ' || *s == '\t' ) {
1251 if( *s >= '0' && *s <= '9' ) {
1252 while( *s >= '0' && *s <= '9' ) {
1253 *value = *value * 10 + (*s - '0');
1266 NextTimeControlFromString (char ** str, long * value)
1269 int result = NextIntegerFromString( str, &temp );
1272 *value = temp * 60; /* Minutes */
1273 if( **str == ':' ) {
1275 result = NextIntegerFromString( str, &temp );
1276 *value += temp; /* Seconds */
1284 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1285 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1286 int result = -1, type = 0; long temp, temp2;
1288 if(**str != ':') return -1; // old params remain in force!
1290 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1291 if( NextIntegerFromString( str, &temp ) ) return -1;
1292 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1295 /* time only: incremental or sudden-death time control */
1296 if(**str == '+') { /* increment follows; read it */
1298 if(**str == '!') type = *(*str)++; // Bronstein TC
1299 if(result = NextIntegerFromString( str, &temp2)) return -1;
1300 *inc = temp2 * 1000;
1301 if(**str == '.') { // read fraction of increment
1302 char *start = ++(*str);
1303 if(result = NextIntegerFromString( str, &temp2)) return -1;
1305 while(start++ < *str) temp2 /= 10;
1309 *moves = 0; *tc = temp * 1000; *incType = type;
1313 (*str)++; /* classical time control */
1314 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1326 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1327 { /* [HGM] get time to add from the multi-session time-control string */
1328 int incType, moves=1; /* kludge to force reading of first session */
1329 long time, increment;
1332 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1334 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1335 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1336 if(movenr == -1) return time; /* last move before new session */
1337 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1338 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1339 if(!moves) return increment; /* current session is incremental */
1340 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1341 } while(movenr >= -1); /* try again for next session */
1343 return 0; // no new time quota on this move
1347 ParseTimeControl (char *tc, float ti, int mps)
1351 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1354 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1355 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1356 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1360 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1362 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1365 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1367 snprintf(buf, MSG_SIZ, ":%s", mytc);
1369 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1371 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1376 /* Parse second time control */
1379 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1387 timeControl_2 = tc2 * 1000;
1397 timeControl = tc1 * 1000;
1400 timeIncrement = ti * 1000; /* convert to ms */
1401 movesPerSession = 0;
1404 movesPerSession = mps;
1412 if (appData.debugMode) {
1413 # ifdef __GIT_VERSION
1414 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1416 fprintf(debugFP, "Version: %s\n", programVersion);
1419 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1421 set_cont_sequence(appData.wrapContSeq);
1422 if (appData.matchGames > 0) {
1423 appData.matchMode = TRUE;
1424 } else if (appData.matchMode) {
1425 appData.matchGames = 1;
1427 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1428 appData.matchGames = appData.sameColorGames;
1429 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1430 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1431 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1434 if (appData.noChessProgram || first.protocolVersion == 1) {
1437 /* kludge: allow timeout for initial "feature" commands */
1439 DisplayMessage("", _("Starting chess program"));
1440 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1445 CalculateIndex (int index, int gameNr)
1446 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1448 if(index > 0) return index; // fixed nmber
1449 if(index == 0) return 1;
1450 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1451 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1456 LoadGameOrPosition (int gameNr)
1457 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1458 if (*appData.loadGameFile != NULLCHAR) {
1459 if (!LoadGameFromFile(appData.loadGameFile,
1460 CalculateIndex(appData.loadGameIndex, gameNr),
1461 appData.loadGameFile, FALSE)) {
1462 DisplayFatalError(_("Bad game file"), 0, 1);
1465 } else if (*appData.loadPositionFile != NULLCHAR) {
1466 if (!LoadPositionFromFile(appData.loadPositionFile,
1467 CalculateIndex(appData.loadPositionIndex, gameNr),
1468 appData.loadPositionFile)) {
1469 DisplayFatalError(_("Bad position file"), 0, 1);
1477 ReserveGame (int gameNr, char resChar)
1479 FILE *tf = fopen(appData.tourneyFile, "r+");
1480 char *p, *q, c, buf[MSG_SIZ];
1481 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1482 safeStrCpy(buf, lastMsg, MSG_SIZ);
1483 DisplayMessage(_("Pick new game"), "");
1484 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1485 ParseArgsFromFile(tf);
1486 p = q = appData.results;
1487 if(appData.debugMode) {
1488 char *r = appData.participants;
1489 fprintf(debugFP, "results = '%s'\n", p);
1490 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1491 fprintf(debugFP, "\n");
1493 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1495 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1496 safeStrCpy(q, p, strlen(p) + 2);
1497 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1498 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1499 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1500 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1503 fseek(tf, -(strlen(p)+4), SEEK_END);
1505 if(c != '"') // depending on DOS or Unix line endings we can be one off
1506 fseek(tf, -(strlen(p)+2), SEEK_END);
1507 else fseek(tf, -(strlen(p)+3), SEEK_END);
1508 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1509 DisplayMessage(buf, "");
1510 free(p); appData.results = q;
1511 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1512 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1513 int round = appData.defaultMatchGames * appData.tourneyType;
1514 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1515 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1516 UnloadEngine(&first); // next game belongs to other pairing;
1517 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1519 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1523 MatchEvent (int mode)
1524 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1526 if(matchMode) { // already in match mode: switch it off
1528 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1531 // if(gameMode != BeginningOfGame) {
1532 // DisplayError(_("You can only start a match from the initial position."), 0);
1536 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1537 /* Set up machine vs. machine match */
1539 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1540 if(appData.tourneyFile[0]) {
1542 if(nextGame > appData.matchGames) {
1544 if(strchr(appData.results, '*') == NULL) {
1546 appData.tourneyCycles++;
1547 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1549 NextTourneyGame(-1, &dummy);
1551 if(nextGame <= appData.matchGames) {
1552 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1554 ScheduleDelayedEvent(NextMatchGame, 10000);
1559 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1560 DisplayError(buf, 0);
1561 appData.tourneyFile[0] = 0;
1565 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1566 DisplayFatalError(_("Can't have a match with no chess programs"),
1571 matchGame = roundNr = 1;
1572 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1576 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1579 InitBackEnd3 P((void))
1581 GameMode initialMode;
1585 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1586 !strcmp(appData.variant, "normal") && // no explicit variant request
1587 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1588 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1589 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1590 char c, *q = first.variants, *p = strchr(q, ',');
1591 if(p) *p = NULLCHAR;
1592 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1594 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1595 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1596 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1597 Reset(TRUE, FALSE); // and re-initialize
1602 InitChessProgram(&first, startedFromSetupPosition);
1604 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1605 free(programVersion);
1606 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1607 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1608 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1611 if (appData.icsActive) {
1613 /* [DM] Make a console window if needed [HGM] merged ifs */
1619 if (*appData.icsCommPort != NULLCHAR)
1620 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1621 appData.icsCommPort);
1623 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1624 appData.icsHost, appData.icsPort);
1626 if( (len >= MSG_SIZ) && appData.debugMode )
1627 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1629 DisplayFatalError(buf, err, 1);
1634 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1636 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1637 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1638 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1639 } else if (appData.noChessProgram) {
1645 if (*appData.cmailGameName != NULLCHAR) {
1647 OpenLoopback(&cmailPR);
1649 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1653 DisplayMessage("", "");
1654 if (StrCaseCmp(appData.initialMode, "") == 0) {
1655 initialMode = BeginningOfGame;
1656 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1657 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1658 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1659 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1662 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1663 initialMode = TwoMachinesPlay;
1664 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1665 initialMode = AnalyzeFile;
1666 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1667 initialMode = AnalyzeMode;
1668 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1669 initialMode = MachinePlaysWhite;
1670 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1671 initialMode = MachinePlaysBlack;
1672 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1673 initialMode = EditGame;
1674 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1675 initialMode = EditPosition;
1676 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1677 initialMode = Training;
1679 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1680 if( (len >= MSG_SIZ) && appData.debugMode )
1681 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1683 DisplayFatalError(buf, 0, 2);
1687 if (appData.matchMode) {
1688 if(appData.tourneyFile[0]) { // start tourney from command line
1690 if(f = fopen(appData.tourneyFile, "r")) {
1691 ParseArgsFromFile(f); // make sure tourney parmeters re known
1693 appData.clockMode = TRUE;
1695 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1698 } else if (*appData.cmailGameName != NULLCHAR) {
1699 /* Set up cmail mode */
1700 ReloadCmailMsgEvent(TRUE);
1702 /* Set up other modes */
1703 if (initialMode == AnalyzeFile) {
1704 if (*appData.loadGameFile == NULLCHAR) {
1705 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1709 if (*appData.loadGameFile != NULLCHAR) {
1710 (void) LoadGameFromFile(appData.loadGameFile,
1711 appData.loadGameIndex,
1712 appData.loadGameFile, TRUE);
1713 } else if (*appData.loadPositionFile != NULLCHAR) {
1714 (void) LoadPositionFromFile(appData.loadPositionFile,
1715 appData.loadPositionIndex,
1716 appData.loadPositionFile);
1717 /* [HGM] try to make self-starting even after FEN load */
1718 /* to allow automatic setup of fairy variants with wtm */
1719 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1720 gameMode = BeginningOfGame;
1721 setboardSpoiledMachineBlack = 1;
1723 /* [HGM] loadPos: make that every new game uses the setup */
1724 /* from file as long as we do not switch variant */
1725 if(!blackPlaysFirst) {
1726 startedFromPositionFile = TRUE;
1727 CopyBoard(filePosition, boards[0]);
1730 if (initialMode == AnalyzeMode) {
1731 if (appData.noChessProgram) {
1732 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1735 if (appData.icsActive) {
1736 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1740 } else if (initialMode == AnalyzeFile) {
1741 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1742 ShowThinkingEvent();
1744 AnalysisPeriodicEvent(1);
1745 } else if (initialMode == MachinePlaysWhite) {
1746 if (appData.noChessProgram) {
1747 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1751 if (appData.icsActive) {
1752 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1756 MachineWhiteEvent();
1757 } else if (initialMode == MachinePlaysBlack) {
1758 if (appData.noChessProgram) {
1759 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1763 if (appData.icsActive) {
1764 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1768 MachineBlackEvent();
1769 } else if (initialMode == TwoMachinesPlay) {
1770 if (appData.noChessProgram) {
1771 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1775 if (appData.icsActive) {
1776 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1781 } else if (initialMode == EditGame) {
1783 } else if (initialMode == EditPosition) {
1784 EditPositionEvent();
1785 } else if (initialMode == Training) {
1786 if (*appData.loadGameFile == NULLCHAR) {
1787 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1796 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1798 DisplayBook(current+1);
1800 MoveHistorySet( movelist, first, last, current, pvInfoList );
1802 EvalGraphSet( first, last, current, pvInfoList );
1804 MakeEngineOutputTitle();
1808 * Establish will establish a contact to a remote host.port.
1809 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1810 * used to talk to the host.
1811 * Returns 0 if okay, error code if not.
1818 if (*appData.icsCommPort != NULLCHAR) {
1819 /* Talk to the host through a serial comm port */
1820 return OpenCommPort(appData.icsCommPort, &icsPR);
1822 } else if (*appData.gateway != NULLCHAR) {
1823 if (*appData.remoteShell == NULLCHAR) {
1824 /* Use the rcmd protocol to run telnet program on a gateway host */
1825 snprintf(buf, sizeof(buf), "%s %s %s",
1826 appData.telnetProgram, appData.icsHost, appData.icsPort);
1827 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1830 /* Use the rsh program to run telnet program on a gateway host */
1831 if (*appData.remoteUser == NULLCHAR) {
1832 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1833 appData.gateway, appData.telnetProgram,
1834 appData.icsHost, appData.icsPort);
1836 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1837 appData.remoteShell, appData.gateway,
1838 appData.remoteUser, appData.telnetProgram,
1839 appData.icsHost, appData.icsPort);
1841 return StartChildProcess(buf, "", &icsPR);
1844 } else if (appData.useTelnet) {
1845 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1848 /* TCP socket interface differs somewhat between
1849 Unix and NT; handle details in the front end.
1851 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1856 EscapeExpand (char *p, char *q)
1857 { // [HGM] initstring: routine to shape up string arguments
1858 while(*p++ = *q++) if(p[-1] == '\\')
1860 case 'n': p[-1] = '\n'; break;
1861 case 'r': p[-1] = '\r'; break;
1862 case 't': p[-1] = '\t'; break;
1863 case '\\': p[-1] = '\\'; break;
1864 case 0: *p = 0; return;
1865 default: p[-1] = q[-1]; break;
1870 show_bytes (FILE *fp, char *buf, int count)
1873 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1874 fprintf(fp, "\\%03o", *buf & 0xff);
1883 /* Returns an errno value */
1885 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1887 char buf[8192], *p, *q, *buflim;
1888 int left, newcount, outcount;
1890 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1891 *appData.gateway != NULLCHAR) {
1892 if (appData.debugMode) {
1893 fprintf(debugFP, ">ICS: ");
1894 show_bytes(debugFP, message, count);
1895 fprintf(debugFP, "\n");
1897 return OutputToProcess(pr, message, count, outError);
1900 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1907 if (appData.debugMode) {
1908 fprintf(debugFP, ">ICS: ");
1909 show_bytes(debugFP, buf, newcount);
1910 fprintf(debugFP, "\n");
1912 outcount = OutputToProcess(pr, buf, newcount, outError);
1913 if (outcount < newcount) return -1; /* to be sure */
1920 } else if (((unsigned char) *p) == TN_IAC) {
1921 *q++ = (char) TN_IAC;
1928 if (appData.debugMode) {
1929 fprintf(debugFP, ">ICS: ");
1930 show_bytes(debugFP, buf, newcount);
1931 fprintf(debugFP, "\n");
1933 outcount = OutputToProcess(pr, buf, newcount, outError);
1934 if (outcount < newcount) return -1; /* to be sure */
1939 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1941 int outError, outCount;
1942 static int gotEof = 0;
1945 /* Pass data read from player on to ICS */
1948 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1949 if (outCount < count) {
1950 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1952 if(have_sent_ICS_logon == 2) {
1953 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1954 fprintf(ini, "%s", message);
1955 have_sent_ICS_logon = 3;
1957 have_sent_ICS_logon = 1;
1958 } else if(have_sent_ICS_logon == 3) {
1959 fprintf(ini, "%s", message);
1961 have_sent_ICS_logon = 1;
1963 } else if (count < 0) {
1964 RemoveInputSource(isr);
1965 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1966 } else if (gotEof++ > 0) {
1967 RemoveInputSource(isr);
1968 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1974 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1975 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1976 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1977 SendToICS("date\n");
1978 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1981 /* added routine for printf style output to ics */
1983 ics_printf (char *format, ...)
1985 char buffer[MSG_SIZ];
1988 va_start(args, format);
1989 vsnprintf(buffer, sizeof(buffer), format, args);
1990 buffer[sizeof(buffer)-1] = '\0';
1998 int count, outCount, outError;
2000 if (icsPR == NoProc) return;
2003 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2004 if (outCount < count) {
2005 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2009 /* This is used for sending logon scripts to the ICS. Sending
2010 without a delay causes problems when using timestamp on ICC
2011 (at least on my machine). */
2013 SendToICSDelayed (char *s, long msdelay)
2015 int count, outCount, outError;
2017 if (icsPR == NoProc) return;
2020 if (appData.debugMode) {
2021 fprintf(debugFP, ">ICS: ");
2022 show_bytes(debugFP, s, count);
2023 fprintf(debugFP, "\n");
2025 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2027 if (outCount < count) {
2028 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2033 /* Remove all highlighting escape sequences in s
2034 Also deletes any suffix starting with '('
2037 StripHighlightAndTitle (char *s)
2039 static char retbuf[MSG_SIZ];
2042 while (*s != NULLCHAR) {
2043 while (*s == '\033') {
2044 while (*s != NULLCHAR && !isalpha(*s)) s++;
2045 if (*s != NULLCHAR) s++;
2047 while (*s != NULLCHAR && *s != '\033') {
2048 if (*s == '(' || *s == '[') {
2059 /* Remove all highlighting escape sequences in s */
2061 StripHighlight (char *s)
2063 static char retbuf[MSG_SIZ];
2066 while (*s != NULLCHAR) {
2067 while (*s == '\033') {
2068 while (*s != NULLCHAR && !isalpha(*s)) s++;
2069 if (*s != NULLCHAR) s++;
2071 while (*s != NULLCHAR && *s != '\033') {
2079 char engineVariant[MSG_SIZ];
2080 char *variantNames[] = VARIANT_NAMES;
2082 VariantName (VariantClass v)
2084 if(v == VariantUnknown || *engineVariant) return engineVariant;
2085 return variantNames[v];
2089 /* Identify a variant from the strings the chess servers use or the
2090 PGN Variant tag names we use. */
2092 StringToVariant (char *e)
2096 VariantClass v = VariantNormal;
2097 int i, found = FALSE;
2103 /* [HGM] skip over optional board-size prefixes */
2104 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2105 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2106 while( *e++ != '_');
2109 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2113 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2114 if (p = StrCaseStr(e, variantNames[i])) {
2115 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2116 v = (VariantClass) i;
2123 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2124 || StrCaseStr(e, "wild/fr")
2125 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2126 v = VariantFischeRandom;
2127 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2128 (i = 1, p = StrCaseStr(e, "w"))) {
2130 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2137 case 0: /* FICS only, actually */
2139 /* Castling legal even if K starts on d-file */
2140 v = VariantWildCastle;
2145 /* Castling illegal even if K & R happen to start in
2146 normal positions. */
2147 v = VariantNoCastle;
2160 /* Castling legal iff K & R start in normal positions */
2166 /* Special wilds for position setup; unclear what to do here */
2167 v = VariantLoadable;
2170 /* Bizarre ICC game */
2171 v = VariantTwoKings;
2174 v = VariantKriegspiel;
2180 v = VariantFischeRandom;
2183 v = VariantCrazyhouse;
2186 v = VariantBughouse;
2192 /* Not quite the same as FICS suicide! */
2193 v = VariantGiveaway;
2199 v = VariantShatranj;
2202 /* Temporary names for future ICC types. The name *will* change in
2203 the next xboard/WinBoard release after ICC defines it. */
2241 v = VariantCapablanca;
2244 v = VariantKnightmate;
2250 v = VariantCylinder;
2256 v = VariantCapaRandom;
2259 v = VariantBerolina;
2271 /* Found "wild" or "w" in the string but no number;
2272 must assume it's normal chess. */
2276 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2277 if( (len >= MSG_SIZ) && appData.debugMode )
2278 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2280 DisplayError(buf, 0);
2286 if (appData.debugMode) {
2287 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2288 e, wnum, VariantName(v));
2293 static int leftover_start = 0, leftover_len = 0;
2294 char star_match[STAR_MATCH_N][MSG_SIZ];
2296 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2297 advance *index beyond it, and set leftover_start to the new value of
2298 *index; else return FALSE. If pattern contains the character '*', it
2299 matches any sequence of characters not containing '\r', '\n', or the
2300 character following the '*' (if any), and the matched sequence(s) are
2301 copied into star_match.
2304 looking_at ( char *buf, int *index, char *pattern)
2306 char *bufp = &buf[*index], *patternp = pattern;
2308 char *matchp = star_match[0];
2311 if (*patternp == NULLCHAR) {
2312 *index = leftover_start = bufp - buf;
2316 if (*bufp == NULLCHAR) return FALSE;
2317 if (*patternp == '*') {
2318 if (*bufp == *(patternp + 1)) {
2320 matchp = star_match[++star_count];
2324 } else if (*bufp == '\n' || *bufp == '\r') {
2326 if (*patternp == NULLCHAR)
2331 *matchp++ = *bufp++;
2335 if (*patternp != *bufp) return FALSE;
2342 SendToPlayer (char *data, int length)
2344 int error, outCount;
2345 outCount = OutputToProcess(NoProc, data, length, &error);
2346 if (outCount < length) {
2347 DisplayFatalError(_("Error writing to display"), error, 1);
2352 PackHolding (char packed[], char *holding)
2362 switch (runlength) {
2373 sprintf(q, "%d", runlength);
2385 /* Telnet protocol requests from the front end */
2387 TelnetRequest (unsigned char ddww, unsigned char option)
2389 unsigned char msg[3];
2390 int outCount, outError;
2392 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2394 if (appData.debugMode) {
2395 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2411 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2420 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2423 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2428 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2430 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2437 if (!appData.icsActive) return;
2438 TelnetRequest(TN_DO, TN_ECHO);
2444 if (!appData.icsActive) return;
2445 TelnetRequest(TN_DONT, TN_ECHO);
2449 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2451 /* put the holdings sent to us by the server on the board holdings area */
2452 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2456 if(gameInfo.holdingsWidth < 2) return;
2457 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2458 return; // prevent overwriting by pre-board holdings
2460 if( (int)lowestPiece >= BlackPawn ) {
2463 holdingsStartRow = BOARD_HEIGHT-1;
2466 holdingsColumn = BOARD_WIDTH-1;
2467 countsColumn = BOARD_WIDTH-2;
2468 holdingsStartRow = 0;
2472 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2473 board[i][holdingsColumn] = EmptySquare;
2474 board[i][countsColumn] = (ChessSquare) 0;
2476 while( (p=*holdings++) != NULLCHAR ) {
2477 piece = CharToPiece( ToUpper(p) );
2478 if(piece == EmptySquare) continue;
2479 /*j = (int) piece - (int) WhitePawn;*/
2480 j = PieceToNumber(piece);
2481 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2482 if(j < 0) continue; /* should not happen */
2483 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2484 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2485 board[holdingsStartRow+j*direction][countsColumn]++;
2491 VariantSwitch (Board board, VariantClass newVariant)
2493 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2494 static Board oldBoard;
2496 startedFromPositionFile = FALSE;
2497 if(gameInfo.variant == newVariant) return;
2499 /* [HGM] This routine is called each time an assignment is made to
2500 * gameInfo.variant during a game, to make sure the board sizes
2501 * are set to match the new variant. If that means adding or deleting
2502 * holdings, we shift the playing board accordingly
2503 * This kludge is needed because in ICS observe mode, we get boards
2504 * of an ongoing game without knowing the variant, and learn about the
2505 * latter only later. This can be because of the move list we requested,
2506 * in which case the game history is refilled from the beginning anyway,
2507 * but also when receiving holdings of a crazyhouse game. In the latter
2508 * case we want to add those holdings to the already received position.
2512 if (appData.debugMode) {
2513 fprintf(debugFP, "Switch board from %s to %s\n",
2514 VariantName(gameInfo.variant), VariantName(newVariant));
2515 setbuf(debugFP, NULL);
2517 shuffleOpenings = 0; /* [HGM] shuffle */
2518 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2522 newWidth = 9; newHeight = 9;
2523 gameInfo.holdingsSize = 7;
2524 case VariantBughouse:
2525 case VariantCrazyhouse:
2526 newHoldingsWidth = 2; break;
2530 newHoldingsWidth = 2;
2531 gameInfo.holdingsSize = 8;
2534 case VariantCapablanca:
2535 case VariantCapaRandom:
2538 newHoldingsWidth = gameInfo.holdingsSize = 0;
2541 if(newWidth != gameInfo.boardWidth ||
2542 newHeight != gameInfo.boardHeight ||
2543 newHoldingsWidth != gameInfo.holdingsWidth ) {
2545 /* shift position to new playing area, if needed */
2546 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2547 for(i=0; i<BOARD_HEIGHT; i++)
2548 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2549 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2551 for(i=0; i<newHeight; i++) {
2552 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2553 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2555 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2556 for(i=0; i<BOARD_HEIGHT; i++)
2557 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2558 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2561 board[HOLDINGS_SET] = 0;
2562 gameInfo.boardWidth = newWidth;
2563 gameInfo.boardHeight = newHeight;
2564 gameInfo.holdingsWidth = newHoldingsWidth;
2565 gameInfo.variant = newVariant;
2566 InitDrawingSizes(-2, 0);
2567 } else gameInfo.variant = newVariant;
2568 CopyBoard(oldBoard, board); // remember correctly formatted board
2569 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2570 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2573 static int loggedOn = FALSE;
2575 /*-- Game start info cache: --*/
2577 char gs_kind[MSG_SIZ];
2578 static char player1Name[128] = "";
2579 static char player2Name[128] = "";
2580 static char cont_seq[] = "\n\\ ";
2581 static int player1Rating = -1;
2582 static int player2Rating = -1;
2583 /*----------------------------*/
2585 ColorClass curColor = ColorNormal;
2586 int suppressKibitz = 0;
2589 Boolean soughtPending = FALSE;
2590 Boolean seekGraphUp;
2591 #define MAX_SEEK_ADS 200
2593 char *seekAdList[MAX_SEEK_ADS];
2594 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2595 float tcList[MAX_SEEK_ADS];
2596 char colorList[MAX_SEEK_ADS];
2597 int nrOfSeekAds = 0;
2598 int minRating = 1010, maxRating = 2800;
2599 int hMargin = 10, vMargin = 20, h, w;
2600 extern int squareSize, lineGap;
2605 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2606 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2607 if(r < minRating+100 && r >=0 ) r = minRating+100;
2608 if(r > maxRating) r = maxRating;
2609 if(tc < 1.f) tc = 1.f;
2610 if(tc > 95.f) tc = 95.f;
2611 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2612 y = ((double)r - minRating)/(maxRating - minRating)
2613 * (h-vMargin-squareSize/8-1) + vMargin;
2614 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2615 if(strstr(seekAdList[i], " u ")) color = 1;
2616 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2617 !strstr(seekAdList[i], "bullet") &&
2618 !strstr(seekAdList[i], "blitz") &&
2619 !strstr(seekAdList[i], "standard") ) color = 2;
2620 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2621 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2625 PlotSingleSeekAd (int i)
2631 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2633 char buf[MSG_SIZ], *ext = "";
2634 VariantClass v = StringToVariant(type);
2635 if(strstr(type, "wild")) {
2636 ext = type + 4; // append wild number
2637 if(v == VariantFischeRandom) type = "chess960"; else
2638 if(v == VariantLoadable) type = "setup"; else
2639 type = VariantName(v);
2641 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2642 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2643 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2644 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2645 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2646 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2647 seekNrList[nrOfSeekAds] = nr;
2648 zList[nrOfSeekAds] = 0;
2649 seekAdList[nrOfSeekAds++] = StrSave(buf);
2650 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2655 EraseSeekDot (int i)
2657 int x = xList[i], y = yList[i], d=squareSize/4, k;
2658 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2659 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2660 // now replot every dot that overlapped
2661 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2662 int xx = xList[k], yy = yList[k];
2663 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2664 DrawSeekDot(xx, yy, colorList[k]);
2669 RemoveSeekAd (int nr)
2672 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2674 if(seekAdList[i]) free(seekAdList[i]);
2675 seekAdList[i] = seekAdList[--nrOfSeekAds];
2676 seekNrList[i] = seekNrList[nrOfSeekAds];
2677 ratingList[i] = ratingList[nrOfSeekAds];
2678 colorList[i] = colorList[nrOfSeekAds];
2679 tcList[i] = tcList[nrOfSeekAds];
2680 xList[i] = xList[nrOfSeekAds];
2681 yList[i] = yList[nrOfSeekAds];
2682 zList[i] = zList[nrOfSeekAds];
2683 seekAdList[nrOfSeekAds] = NULL;
2689 MatchSoughtLine (char *line)
2691 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2692 int nr, base, inc, u=0; char dummy;
2694 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2695 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2697 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2698 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2699 // match: compact and save the line
2700 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2710 if(!seekGraphUp) return FALSE;
2711 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2712 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2714 DrawSeekBackground(0, 0, w, h);
2715 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2716 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2717 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2718 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2720 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2723 snprintf(buf, MSG_SIZ, "%d", i);
2724 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2727 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2728 for(i=1; i<100; i+=(i<10?1:5)) {
2729 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2730 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2731 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2733 snprintf(buf, MSG_SIZ, "%d", i);
2734 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2737 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2742 SeekGraphClick (ClickType click, int x, int y, int moving)
2744 static int lastDown = 0, displayed = 0, lastSecond;
2745 if(y < 0) return FALSE;
2746 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2747 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2748 if(!seekGraphUp) return FALSE;
2749 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2750 DrawPosition(TRUE, NULL);
2753 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2754 if(click == Release || moving) return FALSE;
2756 soughtPending = TRUE;
2757 SendToICS(ics_prefix);
2758 SendToICS("sought\n"); // should this be "sought all"?
2759 } else { // issue challenge based on clicked ad
2760 int dist = 10000; int i, closest = 0, second = 0;
2761 for(i=0; i<nrOfSeekAds; i++) {
2762 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2763 if(d < dist) { dist = d; closest = i; }
2764 second += (d - zList[i] < 120); // count in-range ads
2765 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2769 second = (second > 1);
2770 if(displayed != closest || second != lastSecond) {
2771 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2772 lastSecond = second; displayed = closest;
2774 if(click == Press) {
2775 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2778 } // on press 'hit', only show info
2779 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2780 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2781 SendToICS(ics_prefix);
2783 return TRUE; // let incoming board of started game pop down the graph
2784 } else if(click == Release) { // release 'miss' is ignored
2785 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2786 if(moving == 2) { // right up-click
2787 nrOfSeekAds = 0; // refresh graph
2788 soughtPending = TRUE;
2789 SendToICS(ics_prefix);
2790 SendToICS("sought\n"); // should this be "sought all"?
2793 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2794 // press miss or release hit 'pop down' seek graph
2795 seekGraphUp = FALSE;
2796 DrawPosition(TRUE, NULL);
2802 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2804 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2805 #define STARTED_NONE 0
2806 #define STARTED_MOVES 1
2807 #define STARTED_BOARD 2
2808 #define STARTED_OBSERVE 3
2809 #define STARTED_HOLDINGS 4
2810 #define STARTED_CHATTER 5
2811 #define STARTED_COMMENT 6
2812 #define STARTED_MOVES_NOHIDE 7
2814 static int started = STARTED_NONE;
2815 static char parse[20000];
2816 static int parse_pos = 0;
2817 static char buf[BUF_SIZE + 1];
2818 static int firstTime = TRUE, intfSet = FALSE;
2819 static ColorClass prevColor = ColorNormal;
2820 static int savingComment = FALSE;
2821 static int cmatch = 0; // continuation sequence match
2828 int backup; /* [DM] For zippy color lines */
2830 char talker[MSG_SIZ]; // [HGM] chat
2833 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2835 if (appData.debugMode) {
2837 fprintf(debugFP, "<ICS: ");
2838 show_bytes(debugFP, data, count);
2839 fprintf(debugFP, "\n");
2843 if (appData.debugMode) { int f = forwardMostMove;
2844 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2845 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2846 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2849 /* If last read ended with a partial line that we couldn't parse,
2850 prepend it to the new read and try again. */
2851 if (leftover_len > 0) {
2852 for (i=0; i<leftover_len; i++)
2853 buf[i] = buf[leftover_start + i];
2856 /* copy new characters into the buffer */
2857 bp = buf + leftover_len;
2858 buf_len=leftover_len;
2859 for (i=0; i<count; i++)
2862 if (data[i] == '\r')
2865 // join lines split by ICS?
2866 if (!appData.noJoin)
2869 Joining just consists of finding matches against the
2870 continuation sequence, and discarding that sequence
2871 if found instead of copying it. So, until a match
2872 fails, there's nothing to do since it might be the
2873 complete sequence, and thus, something we don't want
2876 if (data[i] == cont_seq[cmatch])
2879 if (cmatch == strlen(cont_seq))
2881 cmatch = 0; // complete match. just reset the counter
2884 it's possible for the ICS to not include the space
2885 at the end of the last word, making our [correct]
2886 join operation fuse two separate words. the server
2887 does this when the space occurs at the width setting.
2889 if (!buf_len || buf[buf_len-1] != ' ')
2900 match failed, so we have to copy what matched before
2901 falling through and copying this character. In reality,
2902 this will only ever be just the newline character, but
2903 it doesn't hurt to be precise.
2905 strncpy(bp, cont_seq, cmatch);
2917 buf[buf_len] = NULLCHAR;
2918 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2923 while (i < buf_len) {
2924 /* Deal with part of the TELNET option negotiation
2925 protocol. We refuse to do anything beyond the
2926 defaults, except that we allow the WILL ECHO option,
2927 which ICS uses to turn off password echoing when we are
2928 directly connected to it. We reject this option
2929 if localLineEditing mode is on (always on in xboard)
2930 and we are talking to port 23, which might be a real
2931 telnet server that will try to keep WILL ECHO on permanently.
2933 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2934 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2935 unsigned char option;
2937 switch ((unsigned char) buf[++i]) {
2939 if (appData.debugMode)
2940 fprintf(debugFP, "\n<WILL ");
2941 switch (option = (unsigned char) buf[++i]) {
2943 if (appData.debugMode)
2944 fprintf(debugFP, "ECHO ");
2945 /* Reply only if this is a change, according
2946 to the protocol rules. */
2947 if (remoteEchoOption) break;
2948 if (appData.localLineEditing &&
2949 atoi(appData.icsPort) == TN_PORT) {
2950 TelnetRequest(TN_DONT, TN_ECHO);
2953 TelnetRequest(TN_DO, TN_ECHO);
2954 remoteEchoOption = TRUE;
2958 if (appData.debugMode)
2959 fprintf(debugFP, "%d ", option);
2960 /* Whatever this is, we don't want it. */
2961 TelnetRequest(TN_DONT, option);
2966 if (appData.debugMode)
2967 fprintf(debugFP, "\n<WONT ");
2968 switch (option = (unsigned char) buf[++i]) {
2970 if (appData.debugMode)
2971 fprintf(debugFP, "ECHO ");
2972 /* Reply only if this is a change, according
2973 to the protocol rules. */
2974 if (!remoteEchoOption) break;
2976 TelnetRequest(TN_DONT, TN_ECHO);
2977 remoteEchoOption = FALSE;
2980 if (appData.debugMode)
2981 fprintf(debugFP, "%d ", (unsigned char) option);
2982 /* Whatever this is, it must already be turned
2983 off, because we never agree to turn on
2984 anything non-default, so according to the
2985 protocol rules, we don't reply. */
2990 if (appData.debugMode)
2991 fprintf(debugFP, "\n<DO ");
2992 switch (option = (unsigned char) buf[++i]) {
2994 /* Whatever this is, we refuse to do it. */
2995 if (appData.debugMode)
2996 fprintf(debugFP, "%d ", option);
2997 TelnetRequest(TN_WONT, option);
3002 if (appData.debugMode)
3003 fprintf(debugFP, "\n<DONT ");
3004 switch (option = (unsigned char) buf[++i]) {
3006 if (appData.debugMode)
3007 fprintf(debugFP, "%d ", option);
3008 /* Whatever this is, we are already not doing
3009 it, because we never agree to do anything
3010 non-default, so according to the protocol
3011 rules, we don't reply. */
3016 if (appData.debugMode)
3017 fprintf(debugFP, "\n<IAC ");
3018 /* Doubled IAC; pass it through */
3022 if (appData.debugMode)
3023 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3024 /* Drop all other telnet commands on the floor */
3027 if (oldi > next_out)
3028 SendToPlayer(&buf[next_out], oldi - next_out);
3034 /* OK, this at least will *usually* work */
3035 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3039 if (loggedOn && !intfSet) {
3040 if (ics_type == ICS_ICC) {
3041 snprintf(str, MSG_SIZ,
3042 "/set-quietly interface %s\n/set-quietly style 12\n",
3044 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3045 strcat(str, "/set-2 51 1\n/set seek 1\n");
3046 } else if (ics_type == ICS_CHESSNET) {
3047 snprintf(str, MSG_SIZ, "/style 12\n");
3049 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3050 strcat(str, programVersion);
3051 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3052 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3053 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3055 strcat(str, "$iset nohighlight 1\n");
3057 strcat(str, "$iset lock 1\n$style 12\n");
3060 NotifyFrontendLogin();
3064 if (started == STARTED_COMMENT) {
3065 /* Accumulate characters in comment */
3066 parse[parse_pos++] = buf[i];
3067 if (buf[i] == '\n') {
3068 parse[parse_pos] = NULLCHAR;
3069 if(chattingPartner>=0) {
3071 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3072 OutputChatMessage(chattingPartner, mess);
3073 chattingPartner = -1;
3074 next_out = i+1; // [HGM] suppress printing in ICS window
3076 if(!suppressKibitz) // [HGM] kibitz
3077 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3078 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3079 int nrDigit = 0, nrAlph = 0, j;
3080 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3081 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3082 parse[parse_pos] = NULLCHAR;
3083 // try to be smart: if it does not look like search info, it should go to
3084 // ICS interaction window after all, not to engine-output window.
3085 for(j=0; j<parse_pos; j++) { // count letters and digits
3086 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3087 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3088 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3090 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3091 int depth=0; float score;
3092 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3093 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3094 pvInfoList[forwardMostMove-1].depth = depth;
3095 pvInfoList[forwardMostMove-1].score = 100*score;
3097 OutputKibitz(suppressKibitz, parse);
3100 if(gameMode == IcsObserving) // restore original ICS messages
3101 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3102 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3104 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3105 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3106 SendToPlayer(tmp, strlen(tmp));
3108 next_out = i+1; // [HGM] suppress printing in ICS window
3110 started = STARTED_NONE;
3112 /* Don't match patterns against characters in comment */
3117 if (started == STARTED_CHATTER) {
3118 if (buf[i] != '\n') {
3119 /* Don't match patterns against characters in chatter */
3123 started = STARTED_NONE;
3124 if(suppressKibitz) next_out = i+1;
3127 /* Kludge to deal with rcmd protocol */
3128 if (firstTime && looking_at(buf, &i, "\001*")) {
3129 DisplayFatalError(&buf[1], 0, 1);
3135 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3138 if (appData.debugMode)
3139 fprintf(debugFP, "ics_type %d\n", ics_type);
3142 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3143 ics_type = ICS_FICS;
3145 if (appData.debugMode)
3146 fprintf(debugFP, "ics_type %d\n", ics_type);
3149 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3150 ics_type = ICS_CHESSNET;
3152 if (appData.debugMode)
3153 fprintf(debugFP, "ics_type %d\n", ics_type);
3158 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3159 looking_at(buf, &i, "Logging you in as \"*\"") ||
3160 looking_at(buf, &i, "will be \"*\""))) {
3161 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3165 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3167 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3168 DisplayIcsInteractionTitle(buf);
3169 have_set_title = TRUE;
3172 /* skip finger notes */
3173 if (started == STARTED_NONE &&
3174 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3175 (buf[i] == '1' && buf[i+1] == '0')) &&
3176 buf[i+2] == ':' && buf[i+3] == ' ') {
3177 started = STARTED_CHATTER;
3183 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3184 if(appData.seekGraph) {
3185 if(soughtPending && MatchSoughtLine(buf+i)) {
3186 i = strstr(buf+i, "rated") - buf;
3187 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3188 next_out = leftover_start = i;
3189 started = STARTED_CHATTER;
3190 suppressKibitz = TRUE;
3193 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3194 && looking_at(buf, &i, "* ads displayed")) {
3195 soughtPending = FALSE;
3200 if(appData.autoRefresh) {
3201 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3202 int s = (ics_type == ICS_ICC); // ICC format differs
3204 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3205 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3206 looking_at(buf, &i, "*% "); // eat prompt
3207 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3208 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3209 next_out = i; // suppress
3212 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3213 char *p = star_match[0];
3215 if(seekGraphUp) RemoveSeekAd(atoi(p));
3216 while(*p && *p++ != ' '); // next
3218 looking_at(buf, &i, "*% "); // eat prompt
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3226 /* skip formula vars */
3227 if (started == STARTED_NONE &&
3228 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3229 started = STARTED_CHATTER;
3234 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3235 if (appData.autoKibitz && started == STARTED_NONE &&
3236 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3237 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3238 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3239 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3240 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3241 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3242 suppressKibitz = TRUE;
3243 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3245 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3246 && (gameMode == IcsPlayingWhite)) ||
3247 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3248 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3249 started = STARTED_CHATTER; // own kibitz we simply discard
3251 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3252 parse_pos = 0; parse[0] = NULLCHAR;
3253 savingComment = TRUE;
3254 suppressKibitz = gameMode != IcsObserving ? 2 :
3255 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3259 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3260 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3261 && atoi(star_match[0])) {
3262 // suppress the acknowledgements of our own autoKibitz
3264 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3265 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3266 SendToPlayer(star_match[0], strlen(star_match[0]));
3267 if(looking_at(buf, &i, "*% ")) // eat prompt
3268 suppressKibitz = FALSE;
3272 } // [HGM] kibitz: end of patch
3274 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3276 // [HGM] chat: intercept tells by users for which we have an open chat window
3278 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3279 looking_at(buf, &i, "* whispers:") ||
3280 looking_at(buf, &i, "* kibitzes:") ||
3281 looking_at(buf, &i, "* shouts:") ||
3282 looking_at(buf, &i, "* c-shouts:") ||
3283 looking_at(buf, &i, "--> * ") ||
3284 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3285 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3286 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3287 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3289 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3290 chattingPartner = -1;
3292 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3293 for(p=0; p<MAX_CHAT; p++) {
3294 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3295 talker[0] = '['; strcat(talker, "] ");
3296 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3297 chattingPartner = p; break;
3300 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3301 for(p=0; p<MAX_CHAT; p++) {
3302 if(!strcmp("kibitzes", chatPartner[p])) {
3303 talker[0] = '['; strcat(talker, "] ");
3304 chattingPartner = p; break;
3307 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3308 for(p=0; p<MAX_CHAT; p++) {
3309 if(!strcmp("whispers", chatPartner[p])) {
3310 talker[0] = '['; strcat(talker, "] ");
3311 chattingPartner = p; break;
3314 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3315 if(buf[i-8] == '-' && buf[i-3] == 't')
3316 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3317 if(!strcmp("c-shouts", chatPartner[p])) {
3318 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3319 chattingPartner = p; break;
3322 if(chattingPartner < 0)
3323 for(p=0; p<MAX_CHAT; p++) {
3324 if(!strcmp("shouts", chatPartner[p])) {
3325 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3326 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3327 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3328 chattingPartner = p; break;
3332 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3333 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3334 talker[0] = 0; Colorize(ColorTell, FALSE);
3335 chattingPartner = p; break;
3337 if(chattingPartner<0) i = oldi; else {
3338 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3339 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3340 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3341 started = STARTED_COMMENT;
3342 parse_pos = 0; parse[0] = NULLCHAR;
3343 savingComment = 3 + chattingPartner; // counts as TRUE
3344 suppressKibitz = TRUE;
3347 } // [HGM] chat: end of patch
3350 if (appData.zippyTalk || appData.zippyPlay) {
3351 /* [DM] Backup address for color zippy lines */
3353 if (loggedOn == TRUE)
3354 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3355 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3357 } // [DM] 'else { ' deleted
3359 /* Regular tells and says */
3360 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3361 looking_at(buf, &i, "* (your partner) tells you: ") ||
3362 looking_at(buf, &i, "* says: ") ||
3363 /* Don't color "message" or "messages" output */
3364 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3365 looking_at(buf, &i, "*. * at *:*: ") ||
3366 looking_at(buf, &i, "--* (*:*): ") ||
3367 /* Message notifications (same color as tells) */
3368 looking_at(buf, &i, "* has left a message ") ||
3369 looking_at(buf, &i, "* just sent you a message:\n") ||
3370 /* Whispers and kibitzes */
3371 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3372 looking_at(buf, &i, "* kibitzes: ") ||
3374 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3376 if (tkind == 1 && strchr(star_match[0], ':')) {
3377 /* Avoid "tells you:" spoofs in channels */
3380 if (star_match[0][0] == NULLCHAR ||
3381 strchr(star_match[0], ' ') ||
3382 (tkind == 3 && strchr(star_match[1], ' '))) {
3383 /* Reject bogus matches */
3386 if (appData.colorize) {
3387 if (oldi > next_out) {
3388 SendToPlayer(&buf[next_out], oldi - next_out);
3393 Colorize(ColorTell, FALSE);
3394 curColor = ColorTell;
3397 Colorize(ColorKibitz, FALSE);
3398 curColor = ColorKibitz;
3401 p = strrchr(star_match[1], '(');
3408 Colorize(ColorChannel1, FALSE);
3409 curColor = ColorChannel1;
3411 Colorize(ColorChannel, FALSE);
3412 curColor = ColorChannel;
3416 curColor = ColorNormal;
3420 if (started == STARTED_NONE && appData.autoComment &&
3421 (gameMode == IcsObserving ||
3422 gameMode == IcsPlayingWhite ||
3423 gameMode == IcsPlayingBlack)) {
3424 parse_pos = i - oldi;
3425 memcpy(parse, &buf[oldi], parse_pos);
3426 parse[parse_pos] = NULLCHAR;
3427 started = STARTED_COMMENT;
3428 savingComment = TRUE;
3430 started = STARTED_CHATTER;
3431 savingComment = FALSE;
3438 if (looking_at(buf, &i, "* s-shouts: ") ||
3439 looking_at(buf, &i, "* c-shouts: ")) {
3440 if (appData.colorize) {
3441 if (oldi > next_out) {
3442 SendToPlayer(&buf[next_out], oldi - next_out);
3445 Colorize(ColorSShout, FALSE);
3446 curColor = ColorSShout;
3449 started = STARTED_CHATTER;
3453 if (looking_at(buf, &i, "--->")) {
3458 if (looking_at(buf, &i, "* shouts: ") ||
3459 looking_at(buf, &i, "--> ")) {
3460 if (appData.colorize) {
3461 if (oldi > next_out) {
3462 SendToPlayer(&buf[next_out], oldi - next_out);
3465 Colorize(ColorShout, FALSE);
3466 curColor = ColorShout;
3469 started = STARTED_CHATTER;
3473 if (looking_at( buf, &i, "Challenge:")) {
3474 if (appData.colorize) {
3475 if (oldi > next_out) {
3476 SendToPlayer(&buf[next_out], oldi - next_out);
3479 Colorize(ColorChallenge, FALSE);
3480 curColor = ColorChallenge;
3486 if (looking_at(buf, &i, "* offers you") ||
3487 looking_at(buf, &i, "* offers to be") ||
3488 looking_at(buf, &i, "* would like to") ||
3489 looking_at(buf, &i, "* requests to") ||
3490 looking_at(buf, &i, "Your opponent offers") ||
3491 looking_at(buf, &i, "Your opponent requests")) {
3493 if (appData.colorize) {
3494 if (oldi > next_out) {
3495 SendToPlayer(&buf[next_out], oldi - next_out);
3498 Colorize(ColorRequest, FALSE);
3499 curColor = ColorRequest;
3504 if (looking_at(buf, &i, "* (*) seeking")) {
3505 if (appData.colorize) {
3506 if (oldi > next_out) {
3507 SendToPlayer(&buf[next_out], oldi - next_out);
3510 Colorize(ColorSeek, FALSE);
3511 curColor = ColorSeek;
3516 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3518 if (looking_at(buf, &i, "\\ ")) {
3519 if (prevColor != ColorNormal) {
3520 if (oldi > next_out) {
3521 SendToPlayer(&buf[next_out], oldi - next_out);
3524 Colorize(prevColor, TRUE);
3525 curColor = prevColor;
3527 if (savingComment) {
3528 parse_pos = i - oldi;
3529 memcpy(parse, &buf[oldi], parse_pos);
3530 parse[parse_pos] = NULLCHAR;
3531 started = STARTED_COMMENT;
3532 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3533 chattingPartner = savingComment - 3; // kludge to remember the box
3535 started = STARTED_CHATTER;
3540 if (looking_at(buf, &i, "Black Strength :") ||
3541 looking_at(buf, &i, "<<< style 10 board >>>") ||
3542 looking_at(buf, &i, "<10>") ||
3543 looking_at(buf, &i, "#@#")) {
3544 /* Wrong board style */
3546 SendToICS(ics_prefix);
3547 SendToICS("set style 12\n");
3548 SendToICS(ics_prefix);
3549 SendToICS("refresh\n");
3553 if (looking_at(buf, &i, "login:")) {
3554 if (!have_sent_ICS_logon) {
3556 have_sent_ICS_logon = 1;
3557 else // no init script was found
3558 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3559 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3560 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3565 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3566 (looking_at(buf, &i, "\n<12> ") ||
3567 looking_at(buf, &i, "<12> "))) {
3569 if (oldi > next_out) {
3570 SendToPlayer(&buf[next_out], oldi - next_out);
3573 started = STARTED_BOARD;
3578 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3579 looking_at(buf, &i, "<b1> ")) {
3580 if (oldi > next_out) {
3581 SendToPlayer(&buf[next_out], oldi - next_out);
3584 started = STARTED_HOLDINGS;
3589 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3591 /* Header for a move list -- first line */
3593 switch (ics_getting_history) {
3597 case BeginningOfGame:
3598 /* User typed "moves" or "oldmoves" while we
3599 were idle. Pretend we asked for these
3600 moves and soak them up so user can step
3601 through them and/or save them.
3604 gameMode = IcsObserving;
3607 ics_getting_history = H_GOT_UNREQ_HEADER;
3609 case EditGame: /*?*/
3610 case EditPosition: /*?*/
3611 /* Should above feature work in these modes too? */
3612 /* For now it doesn't */
3613 ics_getting_history = H_GOT_UNWANTED_HEADER;
3616 ics_getting_history = H_GOT_UNWANTED_HEADER;
3621 /* Is this the right one? */
3622 if (gameInfo.white && gameInfo.black &&
3623 strcmp(gameInfo.white, star_match[0]) == 0 &&
3624 strcmp(gameInfo.black, star_match[2]) == 0) {
3626 ics_getting_history = H_GOT_REQ_HEADER;
3629 case H_GOT_REQ_HEADER:
3630 case H_GOT_UNREQ_HEADER:
3631 case H_GOT_UNWANTED_HEADER:
3632 case H_GETTING_MOVES:
3633 /* Should not happen */
3634 DisplayError(_("Error gathering move list: two headers"), 0);
3635 ics_getting_history = H_FALSE;
3639 /* Save player ratings into gameInfo if needed */
3640 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3641 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3642 (gameInfo.whiteRating == -1 ||
3643 gameInfo.blackRating == -1)) {
3645 gameInfo.whiteRating = string_to_rating(star_match[1]);
3646 gameInfo.blackRating = string_to_rating(star_match[3]);
3647 if (appData.debugMode)
3648 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3649 gameInfo.whiteRating, gameInfo.blackRating);
3654 if (looking_at(buf, &i,
3655 "* * match, initial time: * minute*, increment: * second")) {
3656 /* Header for a move list -- second line */
3657 /* Initial board will follow if this is a wild game */
3658 if (gameInfo.event != NULL) free(gameInfo.event);
3659 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3660 gameInfo.event = StrSave(str);
3661 /* [HGM] we switched variant. Translate boards if needed. */
3662 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3666 if (looking_at(buf, &i, "Move ")) {
3667 /* Beginning of a move list */
3668 switch (ics_getting_history) {
3670 /* Normally should not happen */
3671 /* Maybe user hit reset while we were parsing */
3674 /* Happens if we are ignoring a move list that is not
3675 * the one we just requested. Common if the user
3676 * tries to observe two games without turning off
3679 case H_GETTING_MOVES:
3680 /* Should not happen */
3681 DisplayError(_("Error gathering move list: nested"), 0);
3682 ics_getting_history = H_FALSE;
3684 case H_GOT_REQ_HEADER:
3685 ics_getting_history = H_GETTING_MOVES;
3686 started = STARTED_MOVES;
3688 if (oldi > next_out) {
3689 SendToPlayer(&buf[next_out], oldi - next_out);
3692 case H_GOT_UNREQ_HEADER:
3693 ics_getting_history = H_GETTING_MOVES;
3694 started = STARTED_MOVES_NOHIDE;
3697 case H_GOT_UNWANTED_HEADER:
3698 ics_getting_history = H_FALSE;
3704 if (looking_at(buf, &i, "% ") ||
3705 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3706 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3707 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3708 soughtPending = FALSE;
3712 if(suppressKibitz) next_out = i;
3713 savingComment = FALSE;
3717 case STARTED_MOVES_NOHIDE:
3718 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3719 parse[parse_pos + i - oldi] = NULLCHAR;
3720 ParseGameHistory(parse);
3722 if (appData.zippyPlay && first.initDone) {
3723 FeedMovesToProgram(&first, forwardMostMove);
3724 if (gameMode == IcsPlayingWhite) {
3725 if (WhiteOnMove(forwardMostMove)) {
3726 if (first.sendTime) {
3727 if (first.useColors) {
3728 SendToProgram("black\n", &first);
3730 SendTimeRemaining(&first, TRUE);
3732 if (first.useColors) {
3733 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3735 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3736 first.maybeThinking = TRUE;
3738 if (first.usePlayother) {
3739 if (first.sendTime) {
3740 SendTimeRemaining(&first, TRUE);
3742 SendToProgram("playother\n", &first);
3748 } else if (gameMode == IcsPlayingBlack) {
3749 if (!WhiteOnMove(forwardMostMove)) {
3750 if (first.sendTime) {
3751 if (first.useColors) {
3752 SendToProgram("white\n", &first);
3754 SendTimeRemaining(&first, FALSE);
3756 if (first.useColors) {
3757 SendToProgram("black\n", &first);
3759 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3760 first.maybeThinking = TRUE;
3762 if (first.usePlayother) {
3763 if (first.sendTime) {
3764 SendTimeRemaining(&first, FALSE);
3766 SendToProgram("playother\n", &first);
3775 if (gameMode == IcsObserving && ics_gamenum == -1) {
3776 /* Moves came from oldmoves or moves command
3777 while we weren't doing anything else.
3779 currentMove = forwardMostMove;
3780 ClearHighlights();/*!!could figure this out*/
3781 flipView = appData.flipView;
3782 DrawPosition(TRUE, boards[currentMove]);
3783 DisplayBothClocks();
3784 snprintf(str, MSG_SIZ, "%s %s %s",
3785 gameInfo.white, _("vs."), gameInfo.black);
3789 /* Moves were history of an active game */
3790 if (gameInfo.resultDetails != NULL) {
3791 free(gameInfo.resultDetails);
3792 gameInfo.resultDetails = NULL;
3795 HistorySet(parseList, backwardMostMove,
3796 forwardMostMove, currentMove-1);
3797 DisplayMove(currentMove - 1);
3798 if (started == STARTED_MOVES) next_out = i;
3799 started = STARTED_NONE;
3800 ics_getting_history = H_FALSE;
3803 case STARTED_OBSERVE:
3804 started = STARTED_NONE;
3805 SendToICS(ics_prefix);
3806 SendToICS("refresh\n");
3812 if(bookHit) { // [HGM] book: simulate book reply
3813 static char bookMove[MSG_SIZ]; // a bit generous?
3815 programStats.nodes = programStats.depth = programStats.time =
3816 programStats.score = programStats.got_only_move = 0;
3817 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3819 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3820 strcat(bookMove, bookHit);
3821 HandleMachineMove(bookMove, &first);
3826 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3827 started == STARTED_HOLDINGS ||
3828 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3829 /* Accumulate characters in move list or board */
3830 parse[parse_pos++] = buf[i];
3833 /* Start of game messages. Mostly we detect start of game
3834 when the first board image arrives. On some versions
3835 of the ICS, though, we need to do a "refresh" after starting
3836 to observe in order to get the current board right away. */
3837 if (looking_at(buf, &i, "Adding game * to observation list")) {
3838 started = STARTED_OBSERVE;
3842 /* Handle auto-observe */
3843 if (appData.autoObserve &&
3844 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3845 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3847 /* Choose the player that was highlighted, if any. */
3848 if (star_match[0][0] == '\033' ||
3849 star_match[1][0] != '\033') {
3850 player = star_match[0];
3852 player = star_match[2];
3854 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3855 ics_prefix, StripHighlightAndTitle(player));
3858 /* Save ratings from notify string */
3859 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3860 player1Rating = string_to_rating(star_match[1]);
3861 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3862 player2Rating = string_to_rating(star_match[3]);
3864 if (appData.debugMode)
3866 "Ratings from 'Game notification:' %s %d, %s %d\n",
3867 player1Name, player1Rating,
3868 player2Name, player2Rating);
3873 /* Deal with automatic examine mode after a game,
3874 and with IcsObserving -> IcsExamining transition */
3875 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3876 looking_at(buf, &i, "has made you an examiner of game *")) {