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 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border; /* [HGM] width of board rim, needed to size seek graph */
300 /* States for ics_getting_history */
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
308 /* whosays values for GameEnds */
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
320 /* Different types of move when calling RegisterMove */
322 #define CMAIL_RESIGN 1
324 #define CMAIL_ACCEPT 3
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
331 /* Telnet protocol constants */
342 safeStrCpy (char *dst, const char *src, size_t count)
345 assert( dst != NULL );
346 assert( src != NULL );
349 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350 if( i == count && dst[count-1] != NULLCHAR)
352 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353 if(appData.debugMode)
354 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
360 /* Some compiler can't cast u64 to double
361 * This function do the job for us:
363 * We use the highest bit for cast, this only
364 * works if the highest bit is not
365 * in use (This should not happen)
367 * We used this for all compiler
370 u64ToDouble (u64 value)
373 u64 tmp = value & u64Const(0x7fffffffffffffff);
374 r = (double)(s64)tmp;
375 if (value & u64Const(0x8000000000000000))
376 r += 9.2233720368547758080e18; /* 2^63 */
380 /* Fake up flags for now, as we aren't keeping track of castling
381 availability yet. [HGM] Change of logic: the flag now only
382 indicates the type of castlings allowed by the rule of the game.
383 The actual rights themselves are maintained in the array
384 castlingRights, as part of the game history, and are not probed
390 int flags = F_ALL_CASTLE_OK;
391 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392 switch (gameInfo.variant) {
394 flags &= ~F_ALL_CASTLE_OK;
395 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396 flags |= F_IGNORE_CHECK;
398 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403 case VariantKriegspiel:
404 flags |= F_KRIEGSPIEL_CAPTURE;
406 case VariantCapaRandom:
407 case VariantFischeRandom:
408 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409 case VariantNoCastle:
410 case VariantShatranj:
415 flags &= ~F_ALL_CASTLE_OK;
418 case VariantChuChess:
420 flags |= F_NULL_MOVE;
425 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
433 [AS] Note: sometimes, the sscanf() function is used to parse the input
434 into a fixed-size buffer. Because of this, we must be prepared to
435 receive strings as long as the size of the input buffer, which is currently
436 set to 4K for Windows and 8K for the rest.
437 So, we must either allocate sufficiently large buffers here, or
438 reduce the size of the input buffer in the input reading part.
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
445 ChessProgramState first, second, pairing;
447 /* premove variables */
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
488 int have_sent_ICS_logon = 0;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
502 /* animateTraining preserves the state of appData.animate
503 * when Training mode is activated. This allows the
504 * response to be animated when appData.animate == TRUE and
505 * appData.animateDragging == TRUE.
507 Boolean animateTraining;
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char initialRights[BOARD_FILES];
517 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int initialRulePlies, FENrulePlies;
519 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
540 ChessSquare FIDEArray[2][BOARD_FILES] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544 BlackKing, BlackBishop, BlackKnight, BlackRook }
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551 BlackKing, BlackKing, BlackKnight, BlackRook }
554 ChessSquare KnightmateArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557 { BlackRook, BlackMan, BlackBishop, BlackQueen,
558 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585 { BlackRook, BlackKnight, BlackMan, BlackFerz,
586 BlackKing, BlackMan, BlackKnight, BlackRook }
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592 { BlackRook, BlackKnight, BlackMan, BlackFerz,
593 BlackKing, BlackMan, BlackKnight, BlackRook }
596 ChessSquare lionArray[2][BOARD_FILES] = {
597 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599 { BlackRook, BlackLion, BlackBishop, BlackQueen,
600 BlackKing, BlackBishop, BlackKnight, BlackRook }
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 #define GothicArray CapablancaArray
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 #define FalconArray CapablancaArray
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
710 Board initialPosition;
713 /* Convert str to a rating. Checks for special cases of "----",
715 "++++", etc. Also strips ()'s */
717 string_to_rating (char *str)
719 while(*str && !isdigit(*str)) ++str;
721 return 0; /* One of the special "no rating" cases */
729 /* Init programStats */
730 programStats.movelist[0] = 0;
731 programStats.depth = 0;
732 programStats.nr_moves = 0;
733 programStats.moves_left = 0;
734 programStats.nodes = 0;
735 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
736 programStats.score = 0;
737 programStats.got_only_move = 0;
738 programStats.got_fail = 0;
739 programStats.line_is_book = 0;
744 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745 if (appData.firstPlaysBlack) {
746 first.twoMachinesColor = "black\n";
747 second.twoMachinesColor = "white\n";
749 first.twoMachinesColor = "white\n";
750 second.twoMachinesColor = "black\n";
753 first.other = &second;
754 second.other = &first;
757 if(appData.timeOddsMode) {
758 norm = appData.timeOdds[0];
759 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
761 first.timeOdds = appData.timeOdds[0]/norm;
762 second.timeOdds = appData.timeOdds[1]/norm;
765 if(programVersion) free(programVersion);
766 if (appData.noChessProgram) {
767 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768 sprintf(programVersion, "%s", PACKAGE_STRING);
770 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777 UnloadEngine (ChessProgramState *cps)
779 /* Kill off first chess program */
780 if (cps->isr != NULL)
781 RemoveInputSource(cps->isr);
784 if (cps->pr != NoProc) {
786 DoSleep( appData.delayBeforeQuit );
787 SendToProgram("quit\n", cps);
788 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 ClearOptions (ChessProgramState *cps)
798 cps->nrOptions = cps->comboCnt = 0;
799 for(i=0; i<MAX_OPTIONS; i++) {
800 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801 cps->option[i].textValue = 0;
805 char *engineNames[] = {
806 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
809 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 InitEngine (ChessProgramState *cps, int n)
816 { // [HGM] all engine initialiation put in a function that does one engine
820 cps->which = engineNames[n];
821 cps->maybeThinking = FALSE;
825 cps->sendDrawOffers = 1;
827 cps->program = appData.chessProgram[n];
828 cps->host = appData.host[n];
829 cps->dir = appData.directory[n];
830 cps->initString = appData.engInitString[n];
831 cps->computerString = appData.computerString[n];
832 cps->useSigint = TRUE;
833 cps->useSigterm = TRUE;
834 cps->reuse = appData.reuse[n];
835 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
836 cps->useSetboard = FALSE;
838 cps->usePing = FALSE;
841 cps->usePlayother = FALSE;
842 cps->useColors = TRUE;
843 cps->useUsermove = FALSE;
844 cps->sendICS = FALSE;
845 cps->sendName = appData.icsActive;
846 cps->sdKludge = FALSE;
847 cps->stKludge = FALSE;
848 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849 TidyProgramName(cps->program, cps->host, cps->tidy);
851 ASSIGN(cps->variants, appData.variant);
852 cps->analysisSupport = 2; /* detect */
853 cps->analyzing = FALSE;
854 cps->initDone = FALSE;
856 cps->pseudo = appData.pseudo[n];
858 /* New features added by Tord: */
859 cps->useFEN960 = FALSE;
860 cps->useOOCastle = TRUE;
861 /* End of new features added by Tord. */
862 cps->fenOverride = appData.fenOverride[n];
864 /* [HGM] time odds: set factor for each machine */
865 cps->timeOdds = appData.timeOdds[n];
867 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868 cps->accumulateTC = appData.accumulateTC[n];
869 cps->maxNrOfSessions = 1;
874 cps->drawDepth = appData.drawDepth[n];
875 cps->supportsNPS = UNKNOWN;
876 cps->memSize = FALSE;
877 cps->maxCores = FALSE;
878 ASSIGN(cps->egtFormats, "");
881 cps->optionSettings = appData.engOptions[n];
883 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884 cps->isUCI = appData.isUCI[n]; /* [AS] */
885 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888 if (appData.protocolVersion[n] > PROTOVER
889 || appData.protocolVersion[n] < 1)
894 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895 appData.protocolVersion[n]);
896 if( (len >= MSG_SIZ) && appData.debugMode )
897 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
899 DisplayFatalError(buf, 0, 2);
903 cps->protocolVersion = appData.protocolVersion[n];
906 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
907 ParseFeatures(appData.featureDefaults, cps);
910 ChessProgramState *savCps;
918 if(WaitForEngine(savCps, LoadEngine)) return;
919 CommonEngineInit(); // recalculate time odds
920 if(gameInfo.variant != StringToVariant(appData.variant)) {
921 // we changed variant when loading the engine; this forces us to reset
922 Reset(TRUE, savCps != &first);
923 oldMode = BeginningOfGame; // to prevent restoring old mode
925 InitChessProgram(savCps, FALSE);
926 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927 DisplayMessage("", "");
928 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 ReplaceEngine (ChessProgramState *cps, int n)
938 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
940 if(oldMode != BeginningOfGame) EditGameEvent();
943 appData.noChessProgram = FALSE;
944 appData.clockMode = TRUE;
947 if(n) return; // only startup first engine immediately; second can wait
948 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
955 static char resetOptions[] =
956 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962 FloatToFront(char **list, char *engineLine)
964 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
966 if(appData.recentEngines <= 0) return;
967 TidyProgramName(engineLine, "localhost", tidy+1);
968 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969 strncpy(buf+1, *list, MSG_SIZ-50);
970 if(p = strstr(buf, tidy)) { // tidy name appears in list
971 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972 while(*p++ = *++q); // squeeze out
974 strcat(tidy, buf+1); // put list behind tidy name
975 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977 ASSIGN(*list, tidy+1);
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983 Load (ChessProgramState *cps, int i)
985 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991 appData.firstProtocolVersion = PROTOVER;
992 ParseArgsFromString(buf);
994 ReplaceEngine(cps, i);
995 FloatToFront(&appData.recentEngineList, engineLine);
999 while(q = strchr(p, SLASH)) p = q+1;
1000 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001 if(engineDir[0] != NULLCHAR) {
1002 ASSIGN(appData.directory[i], engineDir); p = engineName;
1003 } else if(p != engineName) { // derive directory from engine path, when not given
1005 ASSIGN(appData.directory[i], engineName);
1007 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008 } else { ASSIGN(appData.directory[i], "."); }
1009 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1011 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012 snprintf(command, MSG_SIZ, "%s %s", p, params);
1015 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016 ASSIGN(appData.chessProgram[i], p);
1017 appData.isUCI[i] = isUCI;
1018 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019 appData.hasOwnBookUCI[i] = hasBook;
1020 if(!nickName[0]) useNick = FALSE;
1021 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025 q = firstChessProgramNames;
1026 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029 quote, p, quote, appData.directory[i],
1030 useNick ? " -fn \"" : "",
1031 useNick ? nickName : "",
1032 useNick ? "\"" : "",
1033 v1 ? " -firstProtocolVersion 1" : "",
1034 hasBook ? "" : " -fNoOwnBookUCI",
1035 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036 storeVariant ? " -variant " : "",
1037 storeVariant ? VariantName(gameInfo.variant) : "");
1038 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040 if(insert != q) insert[-1] = NULLCHAR;
1041 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1043 FloatToFront(&appData.recentEngineList, buf);
1045 ReplaceEngine(cps, i);
1051 int matched, min, sec;
1053 * Parse timeControl resource
1055 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056 appData.movesPerSession)) {
1058 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059 DisplayFatalError(buf, 0, 2);
1063 * Parse searchTime resource
1065 if (*appData.searchTime != NULLCHAR) {
1066 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1068 searchTime = min * 60;
1069 } else if (matched == 2) {
1070 searchTime = min * 60 + sec;
1073 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074 DisplayFatalError(buf, 0, 2);
1083 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1086 GetTimeMark(&programStartTime);
1087 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088 appData.seedBase = random() + (random()<<15);
1089 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1091 ClearProgramStats();
1092 programStats.ok_to_send = 1;
1093 programStats.seen_stat = 0;
1096 * Initialize game list
1102 * Internet chess server status
1104 if (appData.icsActive) {
1105 appData.matchMode = FALSE;
1106 appData.matchGames = 0;
1108 appData.noChessProgram = !appData.zippyPlay;
1110 appData.zippyPlay = FALSE;
1111 appData.zippyTalk = FALSE;
1112 appData.noChessProgram = TRUE;
1114 if (*appData.icsHelper != NULLCHAR) {
1115 appData.useTelnet = TRUE;
1116 appData.telnetProgram = appData.icsHelper;
1119 appData.zippyTalk = appData.zippyPlay = FALSE;
1122 /* [AS] Initialize pv info list [HGM] and game state */
1126 for( i=0; i<=framePtr; i++ ) {
1127 pvInfoList[i].depth = -1;
1128 boards[i][EP_STATUS] = EP_NONE;
1129 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1135 /* [AS] Adjudication threshold */
1136 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1138 InitEngine(&first, 0);
1139 InitEngine(&second, 1);
1142 pairing.which = "pairing"; // pairing engine
1143 pairing.pr = NoProc;
1145 pairing.program = appData.pairingEngine;
1146 pairing.host = "localhost";
1149 if (appData.icsActive) {
1150 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1151 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152 appData.clockMode = FALSE;
1153 first.sendTime = second.sendTime = 0;
1157 /* Override some settings from environment variables, for backward
1158 compatibility. Unfortunately it's not feasible to have the env
1159 vars just set defaults, at least in xboard. Ugh.
1161 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1166 if (!appData.icsActive) {
1170 /* Check for variants that are supported only in ICS mode,
1171 or not at all. Some that are accepted here nevertheless
1172 have bugs; see comments below.
1174 VariantClass variant = StringToVariant(appData.variant);
1176 case VariantBughouse: /* need four players and two boards */
1177 case VariantKriegspiel: /* need to hide pieces and move details */
1178 /* case VariantFischeRandom: (Fabien: moved below) */
1179 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180 if( (len >= MSG_SIZ) && appData.debugMode )
1181 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1183 DisplayFatalError(buf, 0, 2);
1186 case VariantUnknown:
1187 case VariantLoadable:
1197 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198 if( (len >= MSG_SIZ) && appData.debugMode )
1199 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1201 DisplayFatalError(buf, 0, 2);
1204 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1205 case VariantFairy: /* [HGM] TestLegality definitely off! */
1206 case VariantGothic: /* [HGM] should work */
1207 case VariantCapablanca: /* [HGM] should work */
1208 case VariantCourier: /* [HGM] initial forced moves not implemented */
1209 case VariantShogi: /* [HGM] could still mate with pawn drop */
1210 case VariantChu: /* [HGM] experimental */
1211 case VariantKnightmate: /* [HGM] should work */
1212 case VariantCylinder: /* [HGM] untested */
1213 case VariantFalcon: /* [HGM] untested */
1214 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1215 offboard interposition not understood */
1216 case VariantNormal: /* definitely works! */
1217 case VariantWildCastle: /* pieces not automatically shuffled */
1218 case VariantNoCastle: /* pieces not automatically shuffled */
1219 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1220 case VariantLosers: /* should work except for win condition,
1221 and doesn't know captures are mandatory */
1222 case VariantSuicide: /* should work except for win condition,
1223 and doesn't know captures are mandatory */
1224 case VariantGiveaway: /* should work except for win condition,
1225 and doesn't know captures are mandatory */
1226 case VariantTwoKings: /* should work */
1227 case VariantAtomic: /* should work except for win condition */
1228 case Variant3Check: /* should work except for win condition */
1229 case VariantShatranj: /* should work except for all win conditions */
1230 case VariantMakruk: /* should work except for draw countdown */
1231 case VariantASEAN : /* should work except for draw countdown */
1232 case VariantBerolina: /* might work if TestLegality is off */
1233 case VariantCapaRandom: /* should work */
1234 case VariantJanus: /* should work */
1235 case VariantSuper: /* experimental */
1236 case VariantGreat: /* experimental, requires legality testing to be off */
1237 case VariantSChess: /* S-Chess, should work */
1238 case VariantGrand: /* should work */
1239 case VariantSpartan: /* should work */
1240 case VariantLion: /* should work */
1241 case VariantChuChess: /* should work */
1249 NextIntegerFromString (char ** str, long * value)
1254 while( *s == ' ' || *s == '\t' ) {
1260 if( *s >= '0' && *s <= '9' ) {
1261 while( *s >= '0' && *s <= '9' ) {
1262 *value = *value * 10 + (*s - '0');
1275 NextTimeControlFromString (char ** str, long * value)
1278 int result = NextIntegerFromString( str, &temp );
1281 *value = temp * 60; /* Minutes */
1282 if( **str == ':' ) {
1284 result = NextIntegerFromString( str, &temp );
1285 *value += temp; /* Seconds */
1293 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1294 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1295 int result = -1, type = 0; long temp, temp2;
1297 if(**str != ':') return -1; // old params remain in force!
1299 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1300 if( NextIntegerFromString( str, &temp ) ) return -1;
1301 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1304 /* time only: incremental or sudden-death time control */
1305 if(**str == '+') { /* increment follows; read it */
1307 if(**str == '!') type = *(*str)++; // Bronstein TC
1308 if(result = NextIntegerFromString( str, &temp2)) return -1;
1309 *inc = temp2 * 1000;
1310 if(**str == '.') { // read fraction of increment
1311 char *start = ++(*str);
1312 if(result = NextIntegerFromString( str, &temp2)) return -1;
1314 while(start++ < *str) temp2 /= 10;
1318 *moves = 0; *tc = temp * 1000; *incType = type;
1322 (*str)++; /* classical time control */
1323 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1335 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1336 { /* [HGM] get time to add from the multi-session time-control string */
1337 int incType, moves=1; /* kludge to force reading of first session */
1338 long time, increment;
1341 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1343 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1344 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1345 if(movenr == -1) return time; /* last move before new session */
1346 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1347 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1348 if(!moves) return increment; /* current session is incremental */
1349 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1350 } while(movenr >= -1); /* try again for next session */
1352 return 0; // no new time quota on this move
1356 ParseTimeControl (char *tc, float ti, int mps)
1360 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1363 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1364 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1365 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1369 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1371 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1374 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1376 snprintf(buf, MSG_SIZ, ":%s", mytc);
1378 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1380 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385 /* Parse second time control */
1388 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396 timeControl_2 = tc2 * 1000;
1406 timeControl = tc1 * 1000;
1409 timeIncrement = ti * 1000; /* convert to ms */
1410 movesPerSession = 0;
1413 movesPerSession = mps;
1421 if (appData.debugMode) {
1422 # ifdef __GIT_VERSION
1423 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1425 fprintf(debugFP, "Version: %s\n", programVersion);
1428 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1430 set_cont_sequence(appData.wrapContSeq);
1431 if (appData.matchGames > 0) {
1432 appData.matchMode = TRUE;
1433 } else if (appData.matchMode) {
1434 appData.matchGames = 1;
1436 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1437 appData.matchGames = appData.sameColorGames;
1438 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1439 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1440 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1443 if (appData.noChessProgram || first.protocolVersion == 1) {
1446 /* kludge: allow timeout for initial "feature" commands */
1448 DisplayMessage("", _("Starting chess program"));
1449 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454 CalculateIndex (int index, int gameNr)
1455 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1457 if(index > 0) return index; // fixed nmber
1458 if(index == 0) return 1;
1459 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1460 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465 LoadGameOrPosition (int gameNr)
1466 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1467 if (*appData.loadGameFile != NULLCHAR) {
1468 if (!LoadGameFromFile(appData.loadGameFile,
1469 CalculateIndex(appData.loadGameIndex, gameNr),
1470 appData.loadGameFile, FALSE)) {
1471 DisplayFatalError(_("Bad game file"), 0, 1);
1474 } else if (*appData.loadPositionFile != NULLCHAR) {
1475 if (!LoadPositionFromFile(appData.loadPositionFile,
1476 CalculateIndex(appData.loadPositionIndex, gameNr),
1477 appData.loadPositionFile)) {
1478 DisplayFatalError(_("Bad position file"), 0, 1);
1486 ReserveGame (int gameNr, char resChar)
1488 FILE *tf = fopen(appData.tourneyFile, "r+");
1489 char *p, *q, c, buf[MSG_SIZ];
1490 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1491 safeStrCpy(buf, lastMsg, MSG_SIZ);
1492 DisplayMessage(_("Pick new game"), "");
1493 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1494 ParseArgsFromFile(tf);
1495 p = q = appData.results;
1496 if(appData.debugMode) {
1497 char *r = appData.participants;
1498 fprintf(debugFP, "results = '%s'\n", p);
1499 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1500 fprintf(debugFP, "\n");
1502 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1504 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1505 safeStrCpy(q, p, strlen(p) + 2);
1506 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1507 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1508 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1509 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1512 fseek(tf, -(strlen(p)+4), SEEK_END);
1514 if(c != '"') // depending on DOS or Unix line endings we can be one off
1515 fseek(tf, -(strlen(p)+2), SEEK_END);
1516 else fseek(tf, -(strlen(p)+3), SEEK_END);
1517 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1518 DisplayMessage(buf, "");
1519 free(p); appData.results = q;
1520 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1521 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1522 int round = appData.defaultMatchGames * appData.tourneyType;
1523 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1524 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1525 UnloadEngine(&first); // next game belongs to other pairing;
1526 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1528 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1532 MatchEvent (int mode)
1533 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1535 if(matchMode) { // already in match mode: switch it off
1537 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1540 // if(gameMode != BeginningOfGame) {
1541 // DisplayError(_("You can only start a match from the initial position."), 0);
1545 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1546 /* Set up machine vs. machine match */
1548 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1549 if(appData.tourneyFile[0]) {
1551 if(nextGame > appData.matchGames) {
1553 if(strchr(appData.results, '*') == NULL) {
1555 appData.tourneyCycles++;
1556 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1558 NextTourneyGame(-1, &dummy);
1560 if(nextGame <= appData.matchGames) {
1561 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1563 ScheduleDelayedEvent(NextMatchGame, 10000);
1568 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1569 DisplayError(buf, 0);
1570 appData.tourneyFile[0] = 0;
1574 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1575 DisplayFatalError(_("Can't have a match with no chess programs"),
1580 matchGame = roundNr = 1;
1581 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1585 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1588 InitBackEnd3 P((void))
1590 GameMode initialMode;
1594 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1595 !strcmp(appData.variant, "normal") && // no explicit variant request
1596 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1597 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1598 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1599 char c, *q = first.variants, *p = strchr(q, ',');
1600 if(p) *p = NULLCHAR;
1601 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1603 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1604 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1605 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1606 Reset(TRUE, FALSE); // and re-initialize
1611 InitChessProgram(&first, startedFromSetupPosition);
1613 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1614 free(programVersion);
1615 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1616 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1617 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1620 if (appData.icsActive) {
1622 /* [DM] Make a console window if needed [HGM] merged ifs */
1628 if (*appData.icsCommPort != NULLCHAR)
1629 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1630 appData.icsCommPort);
1632 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1633 appData.icsHost, appData.icsPort);
1635 if( (len >= MSG_SIZ) && appData.debugMode )
1636 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1638 DisplayFatalError(buf, err, 1);
1643 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1645 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1646 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1647 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1648 } else if (appData.noChessProgram) {
1654 if (*appData.cmailGameName != NULLCHAR) {
1656 OpenLoopback(&cmailPR);
1658 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1662 DisplayMessage("", "");
1663 if (StrCaseCmp(appData.initialMode, "") == 0) {
1664 initialMode = BeginningOfGame;
1665 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1666 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1667 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1668 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1671 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1672 initialMode = TwoMachinesPlay;
1673 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1674 initialMode = AnalyzeFile;
1675 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1676 initialMode = AnalyzeMode;
1677 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1678 initialMode = MachinePlaysWhite;
1679 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1680 initialMode = MachinePlaysBlack;
1681 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1682 initialMode = EditGame;
1683 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1684 initialMode = EditPosition;
1685 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1686 initialMode = Training;
1688 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1689 if( (len >= MSG_SIZ) && appData.debugMode )
1690 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1692 DisplayFatalError(buf, 0, 2);
1696 if (appData.matchMode) {
1697 if(appData.tourneyFile[0]) { // start tourney from command line
1699 if(f = fopen(appData.tourneyFile, "r")) {
1700 ParseArgsFromFile(f); // make sure tourney parmeters re known
1702 appData.clockMode = TRUE;
1704 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1707 } else if (*appData.cmailGameName != NULLCHAR) {
1708 /* Set up cmail mode */
1709 ReloadCmailMsgEvent(TRUE);
1711 /* Set up other modes */
1712 if (initialMode == AnalyzeFile) {
1713 if (*appData.loadGameFile == NULLCHAR) {
1714 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1718 if (*appData.loadGameFile != NULLCHAR) {
1719 (void) LoadGameFromFile(appData.loadGameFile,
1720 appData.loadGameIndex,
1721 appData.loadGameFile, TRUE);
1722 } else if (*appData.loadPositionFile != NULLCHAR) {
1723 (void) LoadPositionFromFile(appData.loadPositionFile,
1724 appData.loadPositionIndex,
1725 appData.loadPositionFile);
1726 /* [HGM] try to make self-starting even after FEN load */
1727 /* to allow automatic setup of fairy variants with wtm */
1728 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1729 gameMode = BeginningOfGame;
1730 setboardSpoiledMachineBlack = 1;
1732 /* [HGM] loadPos: make that every new game uses the setup */
1733 /* from file as long as we do not switch variant */
1734 if(!blackPlaysFirst) {
1735 startedFromPositionFile = TRUE;
1736 CopyBoard(filePosition, boards[0]);
1739 if (initialMode == AnalyzeMode) {
1740 if (appData.noChessProgram) {
1741 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1744 if (appData.icsActive) {
1745 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1749 } else if (initialMode == AnalyzeFile) {
1750 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1751 ShowThinkingEvent();
1753 AnalysisPeriodicEvent(1);
1754 } else if (initialMode == MachinePlaysWhite) {
1755 if (appData.noChessProgram) {
1756 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1760 if (appData.icsActive) {
1761 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1765 MachineWhiteEvent();
1766 } else if (initialMode == MachinePlaysBlack) {
1767 if (appData.noChessProgram) {
1768 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1772 if (appData.icsActive) {
1773 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1777 MachineBlackEvent();
1778 } else if (initialMode == TwoMachinesPlay) {
1779 if (appData.noChessProgram) {
1780 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1784 if (appData.icsActive) {
1785 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790 } else if (initialMode == EditGame) {
1792 } else if (initialMode == EditPosition) {
1793 EditPositionEvent();
1794 } else if (initialMode == Training) {
1795 if (*appData.loadGameFile == NULLCHAR) {
1796 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1807 DisplayBook(current+1);
1809 MoveHistorySet( movelist, first, last, current, pvInfoList );
1811 EvalGraphSet( first, last, current, pvInfoList );
1813 MakeEngineOutputTitle();
1817 * Establish will establish a contact to a remote host.port.
1818 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1819 * used to talk to the host.
1820 * Returns 0 if okay, error code if not.
1827 if (*appData.icsCommPort != NULLCHAR) {
1828 /* Talk to the host through a serial comm port */
1829 return OpenCommPort(appData.icsCommPort, &icsPR);
1831 } else if (*appData.gateway != NULLCHAR) {
1832 if (*appData.remoteShell == NULLCHAR) {
1833 /* Use the rcmd protocol to run telnet program on a gateway host */
1834 snprintf(buf, sizeof(buf), "%s %s %s",
1835 appData.telnetProgram, appData.icsHost, appData.icsPort);
1836 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1839 /* Use the rsh program to run telnet program on a gateway host */
1840 if (*appData.remoteUser == NULLCHAR) {
1841 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1842 appData.gateway, appData.telnetProgram,
1843 appData.icsHost, appData.icsPort);
1845 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1846 appData.remoteShell, appData.gateway,
1847 appData.remoteUser, appData.telnetProgram,
1848 appData.icsHost, appData.icsPort);
1850 return StartChildProcess(buf, "", &icsPR);
1853 } else if (appData.useTelnet) {
1854 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1857 /* TCP socket interface differs somewhat between
1858 Unix and NT; handle details in the front end.
1860 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865 EscapeExpand (char *p, char *q)
1866 { // [HGM] initstring: routine to shape up string arguments
1867 while(*p++ = *q++) if(p[-1] == '\\')
1869 case 'n': p[-1] = '\n'; break;
1870 case 'r': p[-1] = '\r'; break;
1871 case 't': p[-1] = '\t'; break;
1872 case '\\': p[-1] = '\\'; break;
1873 case 0: *p = 0; return;
1874 default: p[-1] = q[-1]; break;
1879 show_bytes (FILE *fp, char *buf, int count)
1882 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1883 fprintf(fp, "\\%03o", *buf & 0xff);
1892 /* Returns an errno value */
1894 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1896 char buf[8192], *p, *q, *buflim;
1897 int left, newcount, outcount;
1899 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1900 *appData.gateway != NULLCHAR) {
1901 if (appData.debugMode) {
1902 fprintf(debugFP, ">ICS: ");
1903 show_bytes(debugFP, message, count);
1904 fprintf(debugFP, "\n");
1906 return OutputToProcess(pr, message, count, outError);
1909 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1916 if (appData.debugMode) {
1917 fprintf(debugFP, ">ICS: ");
1918 show_bytes(debugFP, buf, newcount);
1919 fprintf(debugFP, "\n");
1921 outcount = OutputToProcess(pr, buf, newcount, outError);
1922 if (outcount < newcount) return -1; /* to be sure */
1929 } else if (((unsigned char) *p) == TN_IAC) {
1930 *q++ = (char) TN_IAC;
1937 if (appData.debugMode) {
1938 fprintf(debugFP, ">ICS: ");
1939 show_bytes(debugFP, buf, newcount);
1940 fprintf(debugFP, "\n");
1942 outcount = OutputToProcess(pr, buf, newcount, outError);
1943 if (outcount < newcount) return -1; /* to be sure */
1948 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1950 int outError, outCount;
1951 static int gotEof = 0;
1954 /* Pass data read from player on to ICS */
1957 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1958 if (outCount < count) {
1959 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1961 if(have_sent_ICS_logon == 2) {
1962 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1963 fprintf(ini, "%s", message);
1964 have_sent_ICS_logon = 3;
1966 have_sent_ICS_logon = 1;
1967 } else if(have_sent_ICS_logon == 3) {
1968 fprintf(ini, "%s", message);
1970 have_sent_ICS_logon = 1;
1972 } else if (count < 0) {
1973 RemoveInputSource(isr);
1974 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1975 } else if (gotEof++ > 0) {
1976 RemoveInputSource(isr);
1977 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1983 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1984 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1985 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1986 SendToICS("date\n");
1987 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1990 /* added routine for printf style output to ics */
1992 ics_printf (char *format, ...)
1994 char buffer[MSG_SIZ];
1997 va_start(args, format);
1998 vsnprintf(buffer, sizeof(buffer), format, args);
1999 buffer[sizeof(buffer)-1] = '\0';
2007 int count, outCount, outError;
2009 if (icsPR == NoProc) return;
2012 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2013 if (outCount < count) {
2014 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2018 /* This is used for sending logon scripts to the ICS. Sending
2019 without a delay causes problems when using timestamp on ICC
2020 (at least on my machine). */
2022 SendToICSDelayed (char *s, long msdelay)
2024 int count, outCount, outError;
2026 if (icsPR == NoProc) return;
2029 if (appData.debugMode) {
2030 fprintf(debugFP, ">ICS: ");
2031 show_bytes(debugFP, s, count);
2032 fprintf(debugFP, "\n");
2034 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2036 if (outCount < count) {
2037 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042 /* Remove all highlighting escape sequences in s
2043 Also deletes any suffix starting with '('
2046 StripHighlightAndTitle (char *s)
2048 static char retbuf[MSG_SIZ];
2051 while (*s != NULLCHAR) {
2052 while (*s == '\033') {
2053 while (*s != NULLCHAR && !isalpha(*s)) s++;
2054 if (*s != NULLCHAR) s++;
2056 while (*s != NULLCHAR && *s != '\033') {
2057 if (*s == '(' || *s == '[') {
2068 /* Remove all highlighting escape sequences in s */
2070 StripHighlight (char *s)
2072 static char retbuf[MSG_SIZ];
2075 while (*s != NULLCHAR) {
2076 while (*s == '\033') {
2077 while (*s != NULLCHAR && !isalpha(*s)) s++;
2078 if (*s != NULLCHAR) s++;
2080 while (*s != NULLCHAR && *s != '\033') {
2088 char engineVariant[MSG_SIZ];
2089 char *variantNames[] = VARIANT_NAMES;
2091 VariantName (VariantClass v)
2093 if(v == VariantUnknown || *engineVariant) return engineVariant;
2094 return variantNames[v];
2098 /* Identify a variant from the strings the chess servers use or the
2099 PGN Variant tag names we use. */
2101 StringToVariant (char *e)
2105 VariantClass v = VariantNormal;
2106 int i, found = FALSE;
2112 /* [HGM] skip over optional board-size prefixes */
2113 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2114 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2115 while( *e++ != '_');
2118 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2122 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2123 if (p = StrCaseStr(e, variantNames[i])) {
2124 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2125 v = (VariantClass) i;
2132 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2133 || StrCaseStr(e, "wild/fr")
2134 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2135 v = VariantFischeRandom;
2136 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2137 (i = 1, p = StrCaseStr(e, "w"))) {
2139 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2146 case 0: /* FICS only, actually */
2148 /* Castling legal even if K starts on d-file */
2149 v = VariantWildCastle;
2154 /* Castling illegal even if K & R happen to start in
2155 normal positions. */
2156 v = VariantNoCastle;
2169 /* Castling legal iff K & R start in normal positions */
2175 /* Special wilds for position setup; unclear what to do here */
2176 v = VariantLoadable;
2179 /* Bizarre ICC game */
2180 v = VariantTwoKings;
2183 v = VariantKriegspiel;
2189 v = VariantFischeRandom;
2192 v = VariantCrazyhouse;
2195 v = VariantBughouse;
2201 /* Not quite the same as FICS suicide! */
2202 v = VariantGiveaway;
2208 v = VariantShatranj;
2211 /* Temporary names for future ICC types. The name *will* change in
2212 the next xboard/WinBoard release after ICC defines it. */
2250 v = VariantCapablanca;
2253 v = VariantKnightmate;
2259 v = VariantCylinder;
2265 v = VariantCapaRandom;
2268 v = VariantBerolina;
2280 /* Found "wild" or "w" in the string but no number;
2281 must assume it's normal chess. */
2285 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2286 if( (len >= MSG_SIZ) && appData.debugMode )
2287 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2289 DisplayError(buf, 0);
2295 if (appData.debugMode) {
2296 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2297 e, wnum, VariantName(v));
2302 static int leftover_start = 0, leftover_len = 0;
2303 char star_match[STAR_MATCH_N][MSG_SIZ];
2305 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2306 advance *index beyond it, and set leftover_start to the new value of
2307 *index; else return FALSE. If pattern contains the character '*', it
2308 matches any sequence of characters not containing '\r', '\n', or the
2309 character following the '*' (if any), and the matched sequence(s) are
2310 copied into star_match.
2313 looking_at ( char *buf, int *index, char *pattern)
2315 char *bufp = &buf[*index], *patternp = pattern;
2317 char *matchp = star_match[0];
2320 if (*patternp == NULLCHAR) {
2321 *index = leftover_start = bufp - buf;
2325 if (*bufp == NULLCHAR) return FALSE;
2326 if (*patternp == '*') {
2327 if (*bufp == *(patternp + 1)) {
2329 matchp = star_match[++star_count];
2333 } else if (*bufp == '\n' || *bufp == '\r') {
2335 if (*patternp == NULLCHAR)
2340 *matchp++ = *bufp++;
2344 if (*patternp != *bufp) return FALSE;
2351 SendToPlayer (char *data, int length)
2353 int error, outCount;
2354 outCount = OutputToProcess(NoProc, data, length, &error);
2355 if (outCount < length) {
2356 DisplayFatalError(_("Error writing to display"), error, 1);
2361 PackHolding (char packed[], char *holding)
2371 switch (runlength) {
2382 sprintf(q, "%d", runlength);
2394 /* Telnet protocol requests from the front end */
2396 TelnetRequest (unsigned char ddww, unsigned char option)
2398 unsigned char msg[3];
2399 int outCount, outError;
2401 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2403 if (appData.debugMode) {
2404 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2420 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2432 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2439 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2446 if (!appData.icsActive) return;
2447 TelnetRequest(TN_DO, TN_ECHO);
2453 if (!appData.icsActive) return;
2454 TelnetRequest(TN_DONT, TN_ECHO);
2458 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2460 /* put the holdings sent to us by the server on the board holdings area */
2461 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2465 if(gameInfo.holdingsWidth < 2) return;
2466 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2467 return; // prevent overwriting by pre-board holdings
2469 if( (int)lowestPiece >= BlackPawn ) {
2472 holdingsStartRow = BOARD_HEIGHT-1;
2475 holdingsColumn = BOARD_WIDTH-1;
2476 countsColumn = BOARD_WIDTH-2;
2477 holdingsStartRow = 0;
2481 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2482 board[i][holdingsColumn] = EmptySquare;
2483 board[i][countsColumn] = (ChessSquare) 0;
2485 while( (p=*holdings++) != NULLCHAR ) {
2486 piece = CharToPiece( ToUpper(p) );
2487 if(piece == EmptySquare) continue;
2488 /*j = (int) piece - (int) WhitePawn;*/
2489 j = PieceToNumber(piece);
2490 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2491 if(j < 0) continue; /* should not happen */
2492 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2493 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2494 board[holdingsStartRow+j*direction][countsColumn]++;
2500 VariantSwitch (Board board, VariantClass newVariant)
2502 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2503 static Board oldBoard;
2505 startedFromPositionFile = FALSE;
2506 if(gameInfo.variant == newVariant) return;
2508 /* [HGM] This routine is called each time an assignment is made to
2509 * gameInfo.variant during a game, to make sure the board sizes
2510 * are set to match the new variant. If that means adding or deleting
2511 * holdings, we shift the playing board accordingly
2512 * This kludge is needed because in ICS observe mode, we get boards
2513 * of an ongoing game without knowing the variant, and learn about the
2514 * latter only later. This can be because of the move list we requested,
2515 * in which case the game history is refilled from the beginning anyway,
2516 * but also when receiving holdings of a crazyhouse game. In the latter
2517 * case we want to add those holdings to the already received position.
2521 if (appData.debugMode) {
2522 fprintf(debugFP, "Switch board from %s to %s\n",
2523 VariantName(gameInfo.variant), VariantName(newVariant));
2524 setbuf(debugFP, NULL);
2526 shuffleOpenings = 0; /* [HGM] shuffle */
2527 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2531 newWidth = 9; newHeight = 9;
2532 gameInfo.holdingsSize = 7;
2533 case VariantBughouse:
2534 case VariantCrazyhouse:
2535 newHoldingsWidth = 2; break;
2539 newHoldingsWidth = 2;
2540 gameInfo.holdingsSize = 8;
2543 case VariantCapablanca:
2544 case VariantCapaRandom:
2547 newHoldingsWidth = gameInfo.holdingsSize = 0;
2550 if(newWidth != gameInfo.boardWidth ||
2551 newHeight != gameInfo.boardHeight ||
2552 newHoldingsWidth != gameInfo.holdingsWidth ) {
2554 /* shift position to new playing area, if needed */
2555 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2556 for(i=0; i<BOARD_HEIGHT; i++)
2557 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2558 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2560 for(i=0; i<newHeight; i++) {
2561 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2562 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2564 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2565 for(i=0; i<BOARD_HEIGHT; i++)
2566 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2567 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2570 board[HOLDINGS_SET] = 0;
2571 gameInfo.boardWidth = newWidth;
2572 gameInfo.boardHeight = newHeight;
2573 gameInfo.holdingsWidth = newHoldingsWidth;
2574 gameInfo.variant = newVariant;
2575 InitDrawingSizes(-2, 0);
2576 } else gameInfo.variant = newVariant;
2577 CopyBoard(oldBoard, board); // remember correctly formatted board
2578 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2579 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2582 static int loggedOn = FALSE;
2584 /*-- Game start info cache: --*/
2586 char gs_kind[MSG_SIZ];
2587 static char player1Name[128] = "";
2588 static char player2Name[128] = "";
2589 static char cont_seq[] = "\n\\ ";
2590 static int player1Rating = -1;
2591 static int player2Rating = -1;
2592 /*----------------------------*/
2594 ColorClass curColor = ColorNormal;
2595 int suppressKibitz = 0;
2598 Boolean soughtPending = FALSE;
2599 Boolean seekGraphUp;
2600 #define MAX_SEEK_ADS 200
2602 char *seekAdList[MAX_SEEK_ADS];
2603 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2604 float tcList[MAX_SEEK_ADS];
2605 char colorList[MAX_SEEK_ADS];
2606 int nrOfSeekAds = 0;
2607 int minRating = 1010, maxRating = 2800;
2608 int hMargin = 10, vMargin = 20, h, w;
2609 extern int squareSize, lineGap;
2614 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2615 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2616 if(r < minRating+100 && r >=0 ) r = minRating+100;
2617 if(r > maxRating) r = maxRating;
2618 if(tc < 1.f) tc = 1.f;
2619 if(tc > 95.f) tc = 95.f;
2620 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2621 y = ((double)r - minRating)/(maxRating - minRating)
2622 * (h-vMargin-squareSize/8-1) + vMargin;
2623 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2624 if(strstr(seekAdList[i], " u ")) color = 1;
2625 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2626 !strstr(seekAdList[i], "bullet") &&
2627 !strstr(seekAdList[i], "blitz") &&
2628 !strstr(seekAdList[i], "standard") ) color = 2;
2629 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2630 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2634 PlotSingleSeekAd (int i)
2640 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2642 char buf[MSG_SIZ], *ext = "";
2643 VariantClass v = StringToVariant(type);
2644 if(strstr(type, "wild")) {
2645 ext = type + 4; // append wild number
2646 if(v == VariantFischeRandom) type = "chess960"; else
2647 if(v == VariantLoadable) type = "setup"; else
2648 type = VariantName(v);
2650 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2651 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2652 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2653 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2654 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2655 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2656 seekNrList[nrOfSeekAds] = nr;
2657 zList[nrOfSeekAds] = 0;
2658 seekAdList[nrOfSeekAds++] = StrSave(buf);
2659 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664 EraseSeekDot (int i)
2666 int x = xList[i], y = yList[i], d=squareSize/4, k;
2667 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2668 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2669 // now replot every dot that overlapped
2670 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2671 int xx = xList[k], yy = yList[k];
2672 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2673 DrawSeekDot(xx, yy, colorList[k]);
2678 RemoveSeekAd (int nr)
2681 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2683 if(seekAdList[i]) free(seekAdList[i]);
2684 seekAdList[i] = seekAdList[--nrOfSeekAds];
2685 seekNrList[i] = seekNrList[nrOfSeekAds];
2686 ratingList[i] = ratingList[nrOfSeekAds];
2687 colorList[i] = colorList[nrOfSeekAds];
2688 tcList[i] = tcList[nrOfSeekAds];
2689 xList[i] = xList[nrOfSeekAds];
2690 yList[i] = yList[nrOfSeekAds];
2691 zList[i] = zList[nrOfSeekAds];
2692 seekAdList[nrOfSeekAds] = NULL;
2698 MatchSoughtLine (char *line)
2700 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2701 int nr, base, inc, u=0; char dummy;
2703 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2704 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2706 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2707 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2708 // match: compact and save the line
2709 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2719 if(!seekGraphUp) return FALSE;
2720 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2721 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2723 DrawSeekBackground(0, 0, w, h);
2724 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2725 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2726 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2727 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2729 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2732 snprintf(buf, MSG_SIZ, "%d", i);
2733 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2736 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2737 for(i=1; i<100; i+=(i<10?1:5)) {
2738 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2739 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2740 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2742 snprintf(buf, MSG_SIZ, "%d", i);
2743 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2746 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751 SeekGraphClick (ClickType click, int x, int y, int moving)
2753 static int lastDown = 0, displayed = 0, lastSecond;
2754 if(y < 0) return FALSE;
2755 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2756 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2757 if(!seekGraphUp) return FALSE;
2758 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2759 DrawPosition(TRUE, NULL);
2762 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2763 if(click == Release || moving) return FALSE;
2765 soughtPending = TRUE;
2766 SendToICS(ics_prefix);
2767 SendToICS("sought\n"); // should this be "sought all"?
2768 } else { // issue challenge based on clicked ad
2769 int dist = 10000; int i, closest = 0, second = 0;
2770 for(i=0; i<nrOfSeekAds; i++) {
2771 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2772 if(d < dist) { dist = d; closest = i; }
2773 second += (d - zList[i] < 120); // count in-range ads
2774 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2778 second = (second > 1);
2779 if(displayed != closest || second != lastSecond) {
2780 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2781 lastSecond = second; displayed = closest;
2783 if(click == Press) {
2784 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2787 } // on press 'hit', only show info
2788 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2789 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2790 SendToICS(ics_prefix);
2792 return TRUE; // let incoming board of started game pop down the graph
2793 } else if(click == Release) { // release 'miss' is ignored
2794 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2795 if(moving == 2) { // right up-click
2796 nrOfSeekAds = 0; // refresh graph
2797 soughtPending = TRUE;
2798 SendToICS(ics_prefix);
2799 SendToICS("sought\n"); // should this be "sought all"?
2802 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2803 // press miss or release hit 'pop down' seek graph
2804 seekGraphUp = FALSE;
2805 DrawPosition(TRUE, NULL);
2811 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2813 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2814 #define STARTED_NONE 0
2815 #define STARTED_MOVES 1
2816 #define STARTED_BOARD 2
2817 #define STARTED_OBSERVE 3
2818 #define STARTED_HOLDINGS 4
2819 #define STARTED_CHATTER 5
2820 #define STARTED_COMMENT 6
2821 #define STARTED_MOVES_NOHIDE 7
2823 static int started = STARTED_NONE;
2824 static char parse[20000];
2825 static int parse_pos = 0;
2826 static char buf[BUF_SIZE + 1];
2827 static int firstTime = TRUE, intfSet = FALSE;
2828 static ColorClass prevColor = ColorNormal;
2829 static int savingComment = FALSE;
2830 static int cmatch = 0; // continuation sequence match
2837 int backup; /* [DM] For zippy color lines */
2839 char talker[MSG_SIZ]; // [HGM] chat
2840 int channel, collective=0;
2842 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2844 if (appData.debugMode) {
2846 fprintf(debugFP, "<ICS: ");
2847 show_bytes(debugFP, data, count);
2848 fprintf(debugFP, "\n");
2852 if (appData.debugMode) { int f = forwardMostMove;
2853 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2854 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2855 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2858 /* If last read ended with a partial line that we couldn't parse,
2859 prepend it to the new read and try again. */
2860 if (leftover_len > 0) {
2861 for (i=0; i<leftover_len; i++)
2862 buf[i] = buf[leftover_start + i];
2865 /* copy new characters into the buffer */
2866 bp = buf + leftover_len;
2867 buf_len=leftover_len;
2868 for (i=0; i<count; i++)
2871 if (data[i] == '\r')
2874 // join lines split by ICS?
2875 if (!appData.noJoin)
2878 Joining just consists of finding matches against the
2879 continuation sequence, and discarding that sequence
2880 if found instead of copying it. So, until a match
2881 fails, there's nothing to do since it might be the
2882 complete sequence, and thus, something we don't want
2885 if (data[i] == cont_seq[cmatch])
2888 if (cmatch == strlen(cont_seq))
2890 cmatch = 0; // complete match. just reset the counter
2893 it's possible for the ICS to not include the space
2894 at the end of the last word, making our [correct]
2895 join operation fuse two separate words. the server
2896 does this when the space occurs at the width setting.
2898 if (!buf_len || buf[buf_len-1] != ' ')
2909 match failed, so we have to copy what matched before
2910 falling through and copying this character. In reality,
2911 this will only ever be just the newline character, but
2912 it doesn't hurt to be precise.
2914 strncpy(bp, cont_seq, cmatch);
2926 buf[buf_len] = NULLCHAR;
2927 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932 while (i < buf_len) {
2933 /* Deal with part of the TELNET option negotiation
2934 protocol. We refuse to do anything beyond the
2935 defaults, except that we allow the WILL ECHO option,
2936 which ICS uses to turn off password echoing when we are
2937 directly connected to it. We reject this option
2938 if localLineEditing mode is on (always on in xboard)
2939 and we are talking to port 23, which might be a real
2940 telnet server that will try to keep WILL ECHO on permanently.
2942 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2943 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2944 unsigned char option;
2946 switch ((unsigned char) buf[++i]) {
2948 if (appData.debugMode)
2949 fprintf(debugFP, "\n<WILL ");
2950 switch (option = (unsigned char) buf[++i]) {
2952 if (appData.debugMode)
2953 fprintf(debugFP, "ECHO ");
2954 /* Reply only if this is a change, according
2955 to the protocol rules. */
2956 if (remoteEchoOption) break;
2957 if (appData.localLineEditing &&
2958 atoi(appData.icsPort) == TN_PORT) {
2959 TelnetRequest(TN_DONT, TN_ECHO);
2962 TelnetRequest(TN_DO, TN_ECHO);
2963 remoteEchoOption = TRUE;
2967 if (appData.debugMode)
2968 fprintf(debugFP, "%d ", option);
2969 /* Whatever this is, we don't want it. */
2970 TelnetRequest(TN_DONT, option);
2975 if (appData.debugMode)
2976 fprintf(debugFP, "\n<WONT ");
2977 switch (option = (unsigned char) buf[++i]) {
2979 if (appData.debugMode)
2980 fprintf(debugFP, "ECHO ");
2981 /* Reply only if this is a change, according
2982 to the protocol rules. */
2983 if (!remoteEchoOption) break;
2985 TelnetRequest(TN_DONT, TN_ECHO);
2986 remoteEchoOption = FALSE;
2989 if (appData.debugMode)
2990 fprintf(debugFP, "%d ", (unsigned char) option);
2991 /* Whatever this is, it must already be turned
2992 off, because we never agree to turn on
2993 anything non-default, so according to the
2994 protocol rules, we don't reply. */
2999 if (appData.debugMode)
3000 fprintf(debugFP, "\n<DO ");
3001 switch (option = (unsigned char) buf[++i]) {
3003 /* Whatever this is, we refuse to do it. */
3004 if (appData.debugMode)
3005 fprintf(debugFP, "%d ", option);
3006 TelnetRequest(TN_WONT, option);
3011 if (appData.debugMode)
3012 fprintf(debugFP, "\n<DONT ");
3013 switch (option = (unsigned char) buf[++i]) {
3015 if (appData.debugMode)
3016 fprintf(debugFP, "%d ", option);
3017 /* Whatever this is, we are already not doing
3018 it, because we never agree to do anything
3019 non-default, so according to the protocol
3020 rules, we don't reply. */
3025 if (appData.debugMode)
3026 fprintf(debugFP, "\n<IAC ");
3027 /* Doubled IAC; pass it through */
3031 if (appData.debugMode)
3032 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3033 /* Drop all other telnet commands on the floor */
3036 if (oldi > next_out)
3037 SendToPlayer(&buf[next_out], oldi - next_out);
3043 /* OK, this at least will *usually* work */
3044 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3048 if (loggedOn && !intfSet) {
3049 if (ics_type == ICS_ICC) {
3050 snprintf(str, MSG_SIZ,
3051 "/set-quietly interface %s\n/set-quietly style 12\n",
3053 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3054 strcat(str, "/set-2 51 1\n/set seek 1\n");
3055 } else if (ics_type == ICS_CHESSNET) {
3056 snprintf(str, MSG_SIZ, "/style 12\n");
3058 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3059 strcat(str, programVersion);
3060 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3061 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3064 strcat(str, "$iset nohighlight 1\n");
3066 strcat(str, "$iset lock 1\n$style 12\n");
3069 NotifyFrontendLogin();
3073 if (started == STARTED_COMMENT) {
3074 /* Accumulate characters in comment */
3075 parse[parse_pos++] = buf[i];
3076 if (buf[i] == '\n') {
3077 parse[parse_pos] = NULLCHAR;
3078 if(chattingPartner>=0) {
3080 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3081 OutputChatMessage(chattingPartner, mess);
3082 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3084 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3085 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3086 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3087 OutputChatMessage(p, mess);
3091 chattingPartner = -1;
3092 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3095 if(!suppressKibitz) // [HGM] kibitz
3096 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3097 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3098 int nrDigit = 0, nrAlph = 0, j;
3099 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3100 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3101 parse[parse_pos] = NULLCHAR;
3102 // try to be smart: if it does not look like search info, it should go to
3103 // ICS interaction window after all, not to engine-output window.
3104 for(j=0; j<parse_pos; j++) { // count letters and digits
3105 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3106 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3107 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3109 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3110 int depth=0; float score;
3111 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3112 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3113 pvInfoList[forwardMostMove-1].depth = depth;
3114 pvInfoList[forwardMostMove-1].score = 100*score;
3116 OutputKibitz(suppressKibitz, parse);
3119 if(gameMode == IcsObserving) // restore original ICS messages
3120 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3121 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3123 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3124 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3125 SendToPlayer(tmp, strlen(tmp));
3127 next_out = i+1; // [HGM] suppress printing in ICS window
3129 started = STARTED_NONE;
3131 /* Don't match patterns against characters in comment */
3136 if (started == STARTED_CHATTER) {
3137 if (buf[i] != '\n') {
3138 /* Don't match patterns against characters in chatter */
3142 started = STARTED_NONE;
3143 if(suppressKibitz) next_out = i+1;
3146 /* Kludge to deal with rcmd protocol */
3147 if (firstTime && looking_at(buf, &i, "\001*")) {
3148 DisplayFatalError(&buf[1], 0, 1);
3154 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3157 if (appData.debugMode)
3158 fprintf(debugFP, "ics_type %d\n", ics_type);
3161 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3162 ics_type = ICS_FICS;
3164 if (appData.debugMode)
3165 fprintf(debugFP, "ics_type %d\n", ics_type);
3168 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3169 ics_type = ICS_CHESSNET;
3171 if (appData.debugMode)
3172 fprintf(debugFP, "ics_type %d\n", ics_type);
3177 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3178 looking_at(buf, &i, "Logging you in as \"*\"") ||
3179 looking_at(buf, &i, "will be \"*\""))) {
3180 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3184 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3186 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3187 DisplayIcsInteractionTitle(buf);
3188 have_set_title = TRUE;
3191 /* skip finger notes */
3192 if (started == STARTED_NONE &&
3193 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3194 (buf[i] == '1' && buf[i+1] == '0')) &&
3195 buf[i+2] == ':' && buf[i+3] == ' ') {
3196 started = STARTED_CHATTER;
3202 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3203 if(appData.seekGraph) {
3204 if(soughtPending && MatchSoughtLine(buf+i)) {
3205 i = strstr(buf+i, "rated") - buf;
3206 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3207 next_out = leftover_start = i;
3208 started = STARTED_CHATTER;
3209 suppressKibitz = TRUE;
3212 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3213 && looking_at(buf, &i, "* ads displayed")) {
3214 soughtPending = FALSE;
3219 if(appData.autoRefresh) {
3220 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3221 int s = (ics_type == ICS_ICC); // ICC format differs
3223 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3224 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3225 looking_at(buf, &i, "*% "); // eat prompt
3226 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3227 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3228 next_out = i; // suppress
3231 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3232 char *p = star_match[0];
3234 if(seekGraphUp) RemoveSeekAd(atoi(p));
3235 while(*p && *p++ != ' '); // next
3237 looking_at(buf, &i, "*% "); // eat prompt
3238 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3245 /* skip formula vars */
3246 if (started == STARTED_NONE &&
3247 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3248 started = STARTED_CHATTER;
3253 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3254 if (appData.autoKibitz && started == STARTED_NONE &&
3255 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3256 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3257 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3258 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3259 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3260 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3261 suppressKibitz = TRUE;
3262 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3264 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3265 && (gameMode == IcsPlayingWhite)) ||
3266 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3267 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3268 started = STARTED_CHATTER; // own kibitz we simply discard
3270 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3271 parse_pos = 0; parse[0] = NULLCHAR;
3272 savingComment = TRUE;
3273 suppressKibitz = gameMode != IcsObserving ? 2 :
3274 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3278 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3279 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3280 && atoi(star_match[0])) {
3281 // suppress the acknowledgements of our own autoKibitz
3283 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3284 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3285 SendToPlayer(star_match[0], strlen(star_match[0]));
3286 if(looking_at(buf, &i, "*% ")) // eat prompt
3287 suppressKibitz = FALSE;
3291 } // [HGM] kibitz: end of patch
3293 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3295 // [HGM] chat: intercept tells by users for which we have an open chat window
3297 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3298 looking_at(buf, &i, "* whispers:") ||
3299 looking_at(buf, &i, "* kibitzes:") ||
3300 looking_at(buf, &i, "* shouts:") ||
3301 looking_at(buf, &i, "* c-shouts:") ||
3302 looking_at(buf, &i, "--> * ") ||
3303 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3304 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3305 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3306 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3308 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3309 chattingPartner = -1; collective = 0;
3311 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3312 for(p=0; p<MAX_CHAT; p++) {
3314 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3315 talker[0] = '['; strcat(talker, "] ");
3316 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3317 chattingPartner = p; break;
3320 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3321 for(p=0; p<MAX_CHAT; p++) {
3323 if(!strcmp("kibitzes", chatPartner[p])) {
3324 talker[0] = '['; strcat(talker, "] ");
3325 chattingPartner = p; break;
3328 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3329 for(p=0; p<MAX_CHAT; p++) {
3331 if(!strcmp("whispers", chatPartner[p])) {
3332 talker[0] = '['; strcat(talker, "] ");
3333 chattingPartner = p; break;
3336 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3337 if(buf[i-8] == '-' && buf[i-3] == 't')
3338 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3340 if(!strcmp("c-shouts", chatPartner[p])) {
3341 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3342 chattingPartner = p; break;
3345 if(chattingPartner < 0)
3346 for(p=0; p<MAX_CHAT; p++) {
3348 if(!strcmp("shouts", chatPartner[p])) {
3349 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3350 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3351 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3352 chattingPartner = p; break;
3356 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3357 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3359 Colorize(ColorTell, FALSE);
3360 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3362 chattingPartner = p; break;
3364 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3365 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3366 started = STARTED_COMMENT;
3367 parse_pos = 0; parse[0] = NULLCHAR;
3368 savingComment = 3 + chattingPartner; // counts as TRUE
3369 if(collective == 3) i = oldi; else {
3370 suppressKibitz = TRUE;
3371 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3372 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3376 } // [HGM] chat: end of patch
3379 if (appData.zippyTalk || appData.zippyPlay) {
3380 /* [DM] Backup address for color zippy lines */
3382 if (loggedOn == TRUE)
3383 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3384 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3386 } // [DM] 'else { ' deleted
3388 /* Regular tells and says */
3389 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3390 looking_at(buf, &i, "* (your partner) tells you: ") ||
3391 looking_at(buf, &i, "* says: ") ||
3392 /* Don't color "message" or "messages" output */
3393 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3394 looking_at(buf, &i, "*. * at *:*: ") ||
3395 looking_at(buf, &i, "--* (*:*): ") ||
3396 /* Message notifications (same color as tells) */
3397 looking_at(buf, &i, "* has left a message ") ||
3398 looking_at(buf, &i, "* just sent you a message:\n") ||
3399 /* Whispers and kibitzes */
3400 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3401 looking_at(buf, &i, "* kibitzes: ") ||
3403 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3405 if (tkind == 1 && strchr(star_match[0], ':')) {
3406 /* Avoid "tells you:" spoofs in channels */
3409 if (star_match[0][0] == NULLCHAR ||
3410 strchr(star_match[0], ' ') ||
3411 (tkind == 3 && strchr(star_match[1], ' '))) {
3412 /* Reject bogus matches */
3415 if (appData.colorize) {
3416 if (oldi > next_out) {
3417 SendToPlayer(&buf[next_out], oldi - next_out);
3422 Colorize(ColorTell, FALSE);
3423 curColor = ColorTell;
3426 Colorize(ColorKibitz, FALSE);
3427 curColor = ColorKibitz;
3430 p = strrchr(star_match[1], '(');
3437 Colorize(ColorChannel1, FALSE);
3438 curColor = ColorChannel1;
3440 Colorize(ColorChannel, FALSE);
3441 curColor = ColorChannel;
3445 curColor = ColorNormal;
3449 if (started == STARTED_NONE && appData.autoComment &&
3450 (gameMode == IcsObserving ||
3451 gameMode == IcsPlayingWhite ||
3452 gameMode == IcsPlayingBlack)) {
3453 parse_pos = i - oldi;
3454 memcpy(parse, &buf[oldi], parse_pos);
3455 parse[parse_pos] = NULLCHAR;
3456 started = STARTED_COMMENT;
3457 savingComment = TRUE;
3458 } else if(collective != 3) {
3459 started = STARTED_CHATTER;
3460 savingComment = FALSE;
3467 if (looking_at(buf, &i, "* s-shouts: ") ||
3468 looking_at(buf, &i, "* c-shouts: ")) {
3469 if (appData.colorize) {
3470 if (oldi > next_out) {
3471 SendToPlayer(&buf[next_out], oldi - next_out);
3474 Colorize(ColorSShout, FALSE);
3475 curColor = ColorSShout;
3478 started = STARTED_CHATTER;
3482 if (looking_at(buf, &i, "--->")) {
3487 if (looking_at(buf, &i, "* shouts: ") ||
3488 looking_at(buf, &i, "--> ")) {
3489 if (appData.colorize) {
3490 if (oldi > next_out) {
3491 SendToPlayer(&buf[next_out], oldi - next_out);
3494 Colorize(ColorShout, FALSE);
3495 curColor = ColorShout;
3498 started = STARTED_CHATTER;
3502 if (looking_at( buf, &i, "Challenge:")) {
3503 if (appData.colorize) {
3504 if (oldi > next_out) {
3505 SendToPlayer(&buf[next_out], oldi - next_out);
3508 Colorize(ColorChallenge, FALSE);
3509 curColor = ColorChallenge;
3515 if (looking_at(buf, &i, "* offers you") ||
3516 looking_at(buf, &i, "* offers to be") ||
3517 looking_at(buf, &i, "* would like to") ||
3518 looking_at(buf, &i, "* requests to") ||
3519 looking_at(buf, &i, "Your opponent offers") ||
3520 looking_at(buf, &i, "Your opponent requests")) {
3522 if (appData.colorize) {
3523 if (oldi > next_out) {
3524 SendToPlayer(&buf[next_out], oldi - next_out);
3527 Colorize(ColorRequest, FALSE);
3528 curColor = ColorRequest;
3533 if (looking_at(buf, &i, "* (*) seeking")) {
3534 if (appData.colorize) {
3535 if (oldi > next_out) {
3536 SendToPlayer(&buf[next_out], oldi - next_out);
3539 Colorize(ColorSeek, FALSE);
3540 curColor = ColorSeek;
3545 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3547 if (looking_at(buf, &i, "\\ ")) {
3548 if (prevColor != ColorNormal) {
3549 if (oldi > next_out) {
3550 SendToPlayer(&buf[next_out], oldi - next_out);
3553 Colorize(prevColor, TRUE);
3554 curColor = prevColor;
3556 if (savingComment) {
3557 parse_pos = i - oldi;
3558 memcpy(parse, &buf[oldi], parse_pos);
3559 parse[parse_pos] = NULLCHAR;
3560 started = STARTED_COMMENT;
3561 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3562 chattingPartner = savingComment - 3; // kludge to remember the box
3564 started = STARTED_CHATTER;
3569 if (looking_at(buf, &i, "Black Strength :") ||
3570 looking_at(buf, &i, "<<< style 10 board >>>") ||
3571 looking_at(buf, &i, "<10>") ||
3572 looking_at(buf, &i, "#@#")) {
3573 /* Wrong board style */
3575 SendToICS(ics_prefix);
3576 SendToICS("set style 12\n");
3577 SendToICS(ics_prefix);
3578 SendToICS("refresh\n");
3582 if (looking_at(buf, &i, "login:")) {
3583 if (!have_sent_ICS_logon) {
3585 have_sent_ICS_logon = 1;
3586 else // no init script was found
3587 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3588 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3589 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3595 (looking_at(buf, &i, "\n<12> ") ||
3596 looking_at(buf, &i, "<12> "))) {
3598 if (oldi > next_out) {
3599 SendToPlayer(&buf[next_out], oldi - next_out);
3602 started = STARTED_BOARD;
3607 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3608 looking_at(buf, &i, "<b1> ")) {
3609 if (oldi > next_out) {
3610 SendToPlayer(&buf[next_out], oldi - next_out);
3613 started = STARTED_HOLDINGS;
3618 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3620 /* Header for a move list -- first line */
3622 switch (ics_getting_history) {
3626 case BeginningOfGame:
3627 /* User typed "moves" or "oldmoves" while we
3628 were idle. Pretend we asked for these
3629 moves and soak them up so user can step
3630 through them and/or save them.
3633 gameMode = IcsObserving;
3636 ics_getting_history = H_GOT_UNREQ_HEADER;
3638 case EditGame: /*?*/
3639 case EditPosition: /*?*/
3640 /* Should above feature work in these modes too? */
3641 /* For now it doesn't */
3642 ics_getting_history = H_GOT_UNWANTED_HEADER;
3645 ics_getting_history = H_GOT_UNWANTED_HEADER;
3650 /* Is this the right one? */
3651 if (gameInfo.white && gameInfo.black &&
3652 strcmp(gameInfo.white, star_match[0]) == 0 &&
3653 strcmp(gameInfo.black, star_match[2]) == 0) {
3655 ics_getting_history = H_GOT_REQ_HEADER;
3658 case H_GOT_REQ_HEADER:
3659 case H_GOT_UNREQ_HEADER:
3660 case H_GOT_UNWANTED_HEADER:
3661 case H_GETTING_MOVES:
3662 /* Should not happen */
3663 DisplayError(_("Error gathering move list: two headers"), 0);
3664 ics_getting_history = H_FALSE;
3668 /* Save player ratings into gameInfo if needed */
3669 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3670 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3671 (gameInfo.whiteRating == -1 ||
3672 gameInfo.blackRating == -1)) {
3674 gameInfo.whiteRating = string_to_rating(star_match[1]);
3675 gameInfo.blackRating = string_to_rating(star_match[3]);
3676 if (appData.debugMode)
3677 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3678 gameInfo.whiteRating, gameInfo.blackRating);
3683 if (looking_at(buf, &i,
3684 "* * match, initial time: * minute*, increment: * second")) {
3685 /* Header for a move list -- second line */
3686 /* Initial board will follow if this is a wild game */
3687 if (gameInfo.event != NULL) free(gameInfo.event);
3688 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3689 gameInfo.event = StrSave(str);
3690 /* [HGM] we switched variant. Translate boards if needed. */
3691 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3695 if (looking_at(buf, &i, "Move ")) {
3696 /* Beginning of a move list */
3697 switch (ics_getting_history) {
3699 /* Normally should not happen */
3700 /* Maybe user hit reset while we were parsing */
3703 /* Happens if we are ignoring a move list that is not
3704 * the one we just requested. Common if the user
3705 * tries to observe two games without turning off
3708 case H_GETTING_MOVES:
3709 /* Should not happen */
3710 DisplayError(_("Error gathering move list: nested"), 0);
3711 ics_getting_history = H_FALSE;
3713 case H_GOT_REQ_HEADER:
3714 ics_getting_history = H_GETTING_MOVES;
3715 started = STARTED_MOVES;
3717 if (oldi > next_out) {
3718 SendToPlayer(&buf[next_out], oldi - next_out);
3721 case H_GOT_UNREQ_HEADER:
3722 ics_getting_history = H_GETTING_MOVES;
3723 started = STARTED_MOVES_NOHIDE;
3726 case H_GOT_UNWANTED_HEADER:
3727 ics_getting_history = H_FALSE;
3733 if (looking_at(buf, &i, "% ") ||
3734 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3735 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3736 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3737 soughtPending = FALSE;
3741 if(suppressKibitz) next_out = i;
3742 savingComment = FALSE;
3746 case STARTED_MOVES_NOHIDE:
3747 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3748 parse[parse_pos + i - oldi] = NULLCHAR;
3749 ParseGameHistory(parse);
3751 if (appData.zippyPlay && first.initDone) {
3752 FeedMovesToProgram(&first, forwardMostMove);
3753 if (gameMode == IcsPlayingWhite) {
3754 if (WhiteOnMove(forwardMostMove)) {
3755 if (first.sendTime) {
3756 if (first.useColors) {
3757 SendToProgram("black\n", &first);
3759 SendTimeRemaining(&first, TRUE);
3761 if (first.useColors) {
3762 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3764 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3765 first.maybeThinking = TRUE;
3767 if (first.usePlayother) {
3768 if (first.sendTime) {
3769 SendTimeRemaining(&first, TRUE);
3771 SendToProgram("playother\n", &first);
3777 } else if (gameMode == IcsPlayingBlack) {
3778 if (!WhiteOnMove(forwardMostMove)) {
3779 if (first.sendTime) {
3780 if (first.useColors) {
3781 SendToProgram("white\n", &first);
3783 SendTimeRemaining(&first, FALSE);
3785 if (first.useColors) {
3786 SendToProgram("black\n", &first);
3788 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3789 first.maybeThinking = TRUE;
3791 if (first.usePlayother) {
3792 if (first.sendTime) {
3793 SendTimeRemaining(&first, FALSE);
3795 SendToProgram("playother\n", &first);
3804 if (gameMode == IcsObserving && ics_gamenum == -1) {
3805 /* Moves came from oldmoves or moves command
3806 while we weren't doing anything else.
3808 currentMove = forwardMostMove;
3809 ClearHighlights();/*!!could figure this out*/
3810 flipView = appData.flipView;
3811 DrawPosition(TRUE, boards[currentMove]);
3812 DisplayBothClocks();
3813 snprintf(str, MSG_SIZ, "%s %s %s",
3814 gameInfo.white, _("vs."), gameInfo.black);
3818 /* Moves were history of an active game */
3819 if (gameInfo.resultDetails != NULL) {
3820 free(gameInfo.resultDetails);
3821 gameInfo.resultDetails = NULL;
3824 HistorySet(parseList, backwardMostMove,
3825 forwardMostMove, currentMove-1);
3826 DisplayMove(currentMove - 1);
3827 if (started == STARTED_MOVES) next_out = i;
3828 started = STARTED_NONE;
3829 ics_getting_history = H_FALSE;
3832 case STARTED_OBSERVE:
3833 started = STARTED_NONE;
3834 SendToICS(ics_prefix);
3835 SendToICS("refresh\n");
3841 if(bookHit) { // [HGM] book: simulate book reply
3842 static char bookMove[MSG_SIZ]; // a bit generous?
3844 programStats.nodes = programStats.depth = programStats.time =
3845 programStats.score = programStats.got_only_move = 0;
3846 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3848 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3849 strcat(bookMove, bookHit);
3850 HandleMachineMove(bookMove, &first);
3855 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3856 started == STARTED_HOLDINGS ||
3857 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3858 /* Accumulate characters in move list or board */
3859 parse[parse_pos++] = buf[i];
3862 /* Start of game messages. Mostly we detect start of game
3863 when the first board image arrives. On some versions
3864 of the ICS, though, we need to do a "refresh" after starting
3865 to observe in order to get the current board right away. */
3866 if (looking_at(buf, &i, "Adding game * to observation list")) {
3867 started = STARTED_OBSERVE;
3871 /* Handle auto-observe */
3872 if (appData.autoObserve &&
3873 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3874 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {