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"
154 # define _(s) gettext (s)
155 # define N_(s) gettext_noop (s)
156 # define T_(s) gettext(s)
169 int establish P((void));
170 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
171 char *buf, int count, int error));
172 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void SendToICS P((char *s));
175 void SendToICSDelayed P((char *s, long msdelay));
176 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
177 void HandleMachineMove P((char *message, ChessProgramState *cps));
178 int AutoPlayOneMove P((void));
179 int LoadGameOneMove P((ChessMove readAhead));
180 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
181 int LoadPositionFromFile P((char *filename, int n, char *title));
182 int SavePositionToFile P((char *filename));
183 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
184 void ShowMove P((int fromX, int fromY, int toX, int toY));
185 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
186 /*char*/int promoChar));
187 void BackwardInner P((int target));
188 void ForwardInner P((int target));
189 int Adjudicate P((ChessProgramState *cps));
190 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
191 void EditPositionDone P((Boolean fakeRights));
192 void PrintOpponents P((FILE *fp));
193 void PrintPosition P((FILE *fp, int move));
194 void StartChessProgram P((ChessProgramState *cps));
195 void SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198 char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200 int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
249 extern void ConsoleCreate();
252 ChessProgramState *WhitePlayer();
253 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
272 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
298 /* States for ics_getting_history */
300 #define H_REQUESTED 1
301 #define H_GOT_REQ_HEADER 2
302 #define H_GOT_UNREQ_HEADER 3
303 #define H_GETTING_MOVES 4
304 #define H_GOT_UNWANTED_HEADER 5
306 /* whosays values for GameEnds */
315 /* Maximum number of games in a cmail message */
316 #define CMAIL_MAX_GAMES 20
318 /* Different types of move when calling RegisterMove */
320 #define CMAIL_RESIGN 1
322 #define CMAIL_ACCEPT 3
324 /* Different types of result to remember for each game */
325 #define CMAIL_NOT_RESULT 0
326 #define CMAIL_OLD_RESULT 1
327 #define CMAIL_NEW_RESULT 2
329 /* Telnet protocol constants */
340 safeStrCpy (char *dst, const char *src, size_t count)
343 assert( dst != NULL );
344 assert( src != NULL );
347 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
348 if( i == count && dst[count-1] != NULLCHAR)
350 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
351 if(appData.debugMode)
352 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358 /* Some compiler can't cast u64 to double
359 * This function do the job for us:
361 * We use the highest bit for cast, this only
362 * works if the highest bit is not
363 * in use (This should not happen)
365 * We used this for all compiler
368 u64ToDouble (u64 value)
371 u64 tmp = value & u64Const(0x7fffffffffffffff);
372 r = (double)(s64)tmp;
373 if (value & u64Const(0x8000000000000000))
374 r += 9.2233720368547758080e18; /* 2^63 */
378 /* Fake up flags for now, as we aren't keeping track of castling
379 availability yet. [HGM] Change of logic: the flag now only
380 indicates the type of castlings allowed by the rule of the game.
381 The actual rights themselves are maintained in the array
382 castlingRights, as part of the game history, and are not probed
388 int flags = F_ALL_CASTLE_OK;
389 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
390 switch (gameInfo.variant) {
392 flags &= ~F_ALL_CASTLE_OK;
393 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
394 flags |= F_IGNORE_CHECK;
396 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401 case VariantKriegspiel:
402 flags |= F_KRIEGSPIEL_CAPTURE;
404 case VariantCapaRandom:
405 case VariantFischeRandom:
406 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
407 case VariantNoCastle:
408 case VariantShatranj:
413 flags &= ~F_ALL_CASTLE_OK;
421 FILE *gameFileFP, *debugFP, *serverFP;
422 char *currentDebugFile; // [HGM] debug split: to remember name
425 [AS] Note: sometimes, the sscanf() function is used to parse the input
426 into a fixed-size buffer. Because of this, we must be prepared to
427 receive strings as long as the size of the input buffer, which is currently
428 set to 4K for Windows and 8K for the rest.
429 So, we must either allocate sufficiently large buffers here, or
430 reduce the size of the input buffer in the input reading part.
433 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
434 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
435 char thinkOutput1[MSG_SIZ*10];
437 ChessProgramState first, second, pairing;
439 /* premove variables */
442 int premoveFromX = 0;
443 int premoveFromY = 0;
444 int premovePromoChar = 0;
446 Boolean alarmSounded;
447 /* end premove variables */
449 char *ics_prefix = "$";
450 enum ICS_TYPE ics_type = ICS_GENERIC;
452 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
453 int pauseExamForwardMostMove = 0;
454 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
455 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
456 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
457 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
458 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
459 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
460 int whiteFlag = FALSE, blackFlag = FALSE;
461 int userOfferedDraw = FALSE;
462 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
463 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
464 int cmailMoveType[CMAIL_MAX_GAMES];
465 long ics_clock_paused = 0;
466 ProcRef icsPR = NoProc, cmailPR = NoProc;
467 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
468 GameMode gameMode = BeginningOfGame;
469 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
470 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
471 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
472 int hiddenThinkOutputState = 0; /* [AS] */
473 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
474 int adjudicateLossPlies = 6;
475 char white_holding[64], black_holding[64];
476 TimeMark lastNodeCountTime;
477 long lastNodeCount=0;
478 int shiftKey, controlKey; // [HGM] set by mouse handler
480 int have_sent_ICS_logon = 0;
482 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
483 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
484 Boolean adjustedClock;
485 long timeControl_2; /* [AS] Allow separate time controls */
486 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
487 long timeRemaining[2][MAX_MOVES];
488 int matchGame = 0, nextGame = 0, roundNr = 0;
489 Boolean waitingForGame = FALSE, startingEngine = FALSE;
490 TimeMark programStartTime, pauseStart;
491 char ics_handle[MSG_SIZ];
492 int have_set_title = 0;
494 /* animateTraining preserves the state of appData.animate
495 * when Training mode is activated. This allows the
496 * response to be animated when appData.animate == TRUE and
497 * appData.animateDragging == TRUE.
499 Boolean animateTraining;
505 Board boards[MAX_MOVES];
506 /* [HGM] Following 7 needed for accurate legality tests: */
507 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
508 signed char initialRights[BOARD_FILES];
509 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
510 int initialRulePlies, FENrulePlies;
511 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
513 Boolean shuffleOpenings;
514 int mute; // mute all sounds
516 // [HGM] vari: next 12 to save and restore variations
517 #define MAX_VARIATIONS 10
518 int framePtr = MAX_MOVES-1; // points to free stack entry
520 int savedFirst[MAX_VARIATIONS];
521 int savedLast[MAX_VARIATIONS];
522 int savedFramePtr[MAX_VARIATIONS];
523 char *savedDetails[MAX_VARIATIONS];
524 ChessMove savedResult[MAX_VARIATIONS];
526 void PushTail P((int firstMove, int lastMove));
527 Boolean PopTail P((Boolean annotate));
528 void PushInner P((int firstMove, int lastMove));
529 void PopInner P((Boolean annotate));
530 void CleanupTail P((void));
532 ChessSquare FIDEArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackKnight, BlackRook }
539 ChessSquare twoKingsArray[2][BOARD_FILES] = {
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
542 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
543 BlackKing, BlackKing, BlackKnight, BlackRook }
546 ChessSquare KnightmateArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
548 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
549 { BlackRook, BlackMan, BlackBishop, BlackQueen,
550 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
553 ChessSquare SpartanArray[2][BOARD_FILES] = {
554 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
555 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
556 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
557 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
560 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
561 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
562 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
563 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
564 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
567 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
568 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
569 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
570 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
571 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
575 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
576 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
577 { BlackRook, BlackKnight, BlackMan, BlackFerz,
578 BlackKing, BlackMan, BlackKnight, BlackRook }
581 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
582 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
583 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
584 { BlackRook, BlackKnight, BlackMan, BlackFerz,
585 BlackKing, BlackMan, BlackKnight, BlackRook }
588 ChessSquare lionArray[2][BOARD_FILES] = {
589 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
590 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
591 { BlackRook, BlackLion, BlackBishop, BlackQueen,
592 BlackKing, BlackBishop, BlackKnight, BlackRook }
596 #if (BOARD_FILES>=10)
597 ChessSquare ShogiArray[2][BOARD_FILES] = {
598 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
599 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
600 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
601 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
604 ChessSquare XiangqiArray[2][BOARD_FILES] = {
605 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
606 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
607 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
608 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
611 ChessSquare CapablancaArray[2][BOARD_FILES] = {
612 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
613 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
614 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
615 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
618 ChessSquare GreatArray[2][BOARD_FILES] = {
619 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
620 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
621 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
622 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
625 ChessSquare JanusArray[2][BOARD_FILES] = {
626 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
627 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
628 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
629 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
632 ChessSquare GrandArray[2][BOARD_FILES] = {
633 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
634 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
635 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
636 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
639 ChessSquare ChuChessArray[2][BOARD_FILES] = {
640 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
641 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
642 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
643 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
647 ChessSquare GothicArray[2][BOARD_FILES] = {
648 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
649 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
650 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
651 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
654 #define GothicArray CapablancaArray
658 ChessSquare FalconArray[2][BOARD_FILES] = {
659 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
660 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
661 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
662 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
665 #define FalconArray CapablancaArray
668 #else // !(BOARD_FILES>=10)
669 #define XiangqiPosition FIDEArray
670 #define CapablancaArray FIDEArray
671 #define GothicArray FIDEArray
672 #define GreatArray FIDEArray
673 #endif // !(BOARD_FILES>=10)
675 #if (BOARD_FILES>=12)
676 ChessSquare CourierArray[2][BOARD_FILES] = {
677 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
678 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
679 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
680 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
682 ChessSquare ChuArray[6][BOARD_FILES] = {
683 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
684 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
685 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
686 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
687 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
688 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
689 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
690 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
691 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
692 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
693 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
694 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
696 #else // !(BOARD_FILES>=12)
697 #define CourierArray CapablancaArray
698 #define ChuArray CapablancaArray
699 #endif // !(BOARD_FILES>=12)
702 Board initialPosition;
705 /* Convert str to a rating. Checks for special cases of "----",
707 "++++", etc. Also strips ()'s */
709 string_to_rating (char *str)
711 while(*str && !isdigit(*str)) ++str;
713 return 0; /* One of the special "no rating" cases */
721 /* Init programStats */
722 programStats.movelist[0] = 0;
723 programStats.depth = 0;
724 programStats.nr_moves = 0;
725 programStats.moves_left = 0;
726 programStats.nodes = 0;
727 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
728 programStats.score = 0;
729 programStats.got_only_move = 0;
730 programStats.got_fail = 0;
731 programStats.line_is_book = 0;
736 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
737 if (appData.firstPlaysBlack) {
738 first.twoMachinesColor = "black\n";
739 second.twoMachinesColor = "white\n";
741 first.twoMachinesColor = "white\n";
742 second.twoMachinesColor = "black\n";
745 first.other = &second;
746 second.other = &first;
749 if(appData.timeOddsMode) {
750 norm = appData.timeOdds[0];
751 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
753 first.timeOdds = appData.timeOdds[0]/norm;
754 second.timeOdds = appData.timeOdds[1]/norm;
757 if(programVersion) free(programVersion);
758 if (appData.noChessProgram) {
759 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
760 sprintf(programVersion, "%s", PACKAGE_STRING);
762 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
763 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
764 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
769 UnloadEngine (ChessProgramState *cps)
771 /* Kill off first chess program */
772 if (cps->isr != NULL)
773 RemoveInputSource(cps->isr);
776 if (cps->pr != NoProc) {
778 DoSleep( appData.delayBeforeQuit );
779 SendToProgram("quit\n", cps);
780 DoSleep( appData.delayAfterQuit );
781 DestroyChildProcess(cps->pr, cps->useSigterm);
784 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
788 ClearOptions (ChessProgramState *cps)
791 cps->nrOptions = cps->comboCnt = 0;
792 for(i=0; i<MAX_OPTIONS; i++) {
793 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
794 cps->option[i].textValue = 0;
798 char *engineNames[] = {
799 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
800 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
802 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
803 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 InitEngine (ChessProgramState *cps, int n)
809 { // [HGM] all engine initialiation put in a function that does one engine
813 cps->which = engineNames[n];
814 cps->maybeThinking = FALSE;
818 cps->sendDrawOffers = 1;
820 cps->program = appData.chessProgram[n];
821 cps->host = appData.host[n];
822 cps->dir = appData.directory[n];
823 cps->initString = appData.engInitString[n];
824 cps->computerString = appData.computerString[n];
825 cps->useSigint = TRUE;
826 cps->useSigterm = TRUE;
827 cps->reuse = appData.reuse[n];
828 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
829 cps->useSetboard = FALSE;
831 cps->usePing = FALSE;
834 cps->usePlayother = FALSE;
835 cps->useColors = TRUE;
836 cps->useUsermove = FALSE;
837 cps->sendICS = FALSE;
838 cps->sendName = appData.icsActive;
839 cps->sdKludge = FALSE;
840 cps->stKludge = FALSE;
841 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
842 TidyProgramName(cps->program, cps->host, cps->tidy);
844 ASSIGN(cps->variants, appData.variant);
845 cps->analysisSupport = 2; /* detect */
846 cps->analyzing = FALSE;
847 cps->initDone = FALSE;
850 /* New features added by Tord: */
851 cps->useFEN960 = FALSE;
852 cps->useOOCastle = TRUE;
853 /* End of new features added by Tord. */
854 cps->fenOverride = appData.fenOverride[n];
856 /* [HGM] time odds: set factor for each machine */
857 cps->timeOdds = appData.timeOdds[n];
859 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
860 cps->accumulateTC = appData.accumulateTC[n];
861 cps->maxNrOfSessions = 1;
866 cps->drawDepth = appData.drawDepth[n];
867 cps->supportsNPS = UNKNOWN;
868 cps->memSize = FALSE;
869 cps->maxCores = FALSE;
870 ASSIGN(cps->egtFormats, "");
873 cps->optionSettings = appData.engOptions[n];
875 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
876 cps->isUCI = appData.isUCI[n]; /* [AS] */
877 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
880 if (appData.protocolVersion[n] > PROTOVER
881 || appData.protocolVersion[n] < 1)
886 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
887 appData.protocolVersion[n]);
888 if( (len >= MSG_SIZ) && appData.debugMode )
889 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
891 DisplayFatalError(buf, 0, 2);
895 cps->protocolVersion = appData.protocolVersion[n];
898 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
899 ParseFeatures(appData.featureDefaults, cps);
902 ChessProgramState *savCps;
910 if(WaitForEngine(savCps, LoadEngine)) return;
911 CommonEngineInit(); // recalculate time odds
912 if(gameInfo.variant != StringToVariant(appData.variant)) {
913 // we changed variant when loading the engine; this forces us to reset
914 Reset(TRUE, savCps != &first);
915 oldMode = BeginningOfGame; // to prevent restoring old mode
917 InitChessProgram(savCps, FALSE);
918 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
919 DisplayMessage("", "");
920 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
921 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
924 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
928 ReplaceEngine (ChessProgramState *cps, int n)
930 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
932 if(oldMode != BeginningOfGame) EditGameEvent();
935 appData.noChessProgram = FALSE;
936 appData.clockMode = TRUE;
939 if(n) return; // only startup first engine immediately; second can wait
940 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
944 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
945 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
947 static char resetOptions[] =
948 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
949 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
950 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
951 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
954 FloatToFront(char **list, char *engineLine)
956 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
958 if(appData.recentEngines <= 0) return;
959 TidyProgramName(engineLine, "localhost", tidy+1);
960 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
961 strncpy(buf+1, *list, MSG_SIZ-50);
962 if(p = strstr(buf, tidy)) { // tidy name appears in list
963 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
964 while(*p++ = *++q); // squeeze out
966 strcat(tidy, buf+1); // put list behind tidy name
967 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
968 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
969 ASSIGN(*list, tidy+1);
972 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
975 Load (ChessProgramState *cps, int i)
977 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
978 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
979 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
980 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
981 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
982 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
983 appData.firstProtocolVersion = PROTOVER;
984 ParseArgsFromString(buf);
986 ReplaceEngine(cps, i);
987 FloatToFront(&appData.recentEngineList, engineLine);
991 while(q = strchr(p, SLASH)) p = q+1;
992 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
993 if(engineDir[0] != NULLCHAR) {
994 ASSIGN(appData.directory[i], engineDir); p = engineName;
995 } else if(p != engineName) { // derive directory from engine path, when not given
997 ASSIGN(appData.directory[i], engineName);
999 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1000 } else { ASSIGN(appData.directory[i], "."); }
1001 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1003 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1004 snprintf(command, MSG_SIZ, "%s %s", p, params);
1007 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1008 ASSIGN(appData.chessProgram[i], p);
1009 appData.isUCI[i] = isUCI;
1010 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1011 appData.hasOwnBookUCI[i] = hasBook;
1012 if(!nickName[0]) useNick = FALSE;
1013 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1017 q = firstChessProgramNames;
1018 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1019 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1020 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1021 quote, p, quote, appData.directory[i],
1022 useNick ? " -fn \"" : "",
1023 useNick ? nickName : "",
1024 useNick ? "\"" : "",
1025 v1 ? " -firstProtocolVersion 1" : "",
1026 hasBook ? "" : " -fNoOwnBookUCI",
1027 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1028 storeVariant ? " -variant " : "",
1029 storeVariant ? VariantName(gameInfo.variant) : "");
1030 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1031 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1032 if(insert != q) insert[-1] = NULLCHAR;
1033 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1035 FloatToFront(&appData.recentEngineList, buf);
1037 ReplaceEngine(cps, i);
1043 int matched, min, sec;
1045 * Parse timeControl resource
1047 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1048 appData.movesPerSession)) {
1050 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1051 DisplayFatalError(buf, 0, 2);
1055 * Parse searchTime resource
1057 if (*appData.searchTime != NULLCHAR) {
1058 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1060 searchTime = min * 60;
1061 } else if (matched == 2) {
1062 searchTime = min * 60 + sec;
1065 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1066 DisplayFatalError(buf, 0, 2);
1075 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1076 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1078 GetTimeMark(&programStartTime);
1079 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1080 appData.seedBase = random() + (random()<<15);
1081 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1083 ClearProgramStats();
1084 programStats.ok_to_send = 1;
1085 programStats.seen_stat = 0;
1088 * Initialize game list
1094 * Internet chess server status
1096 if (appData.icsActive) {
1097 appData.matchMode = FALSE;
1098 appData.matchGames = 0;
1100 appData.noChessProgram = !appData.zippyPlay;
1102 appData.zippyPlay = FALSE;
1103 appData.zippyTalk = FALSE;
1104 appData.noChessProgram = TRUE;
1106 if (*appData.icsHelper != NULLCHAR) {
1107 appData.useTelnet = TRUE;
1108 appData.telnetProgram = appData.icsHelper;
1111 appData.zippyTalk = appData.zippyPlay = FALSE;
1114 /* [AS] Initialize pv info list [HGM] and game state */
1118 for( i=0; i<=framePtr; i++ ) {
1119 pvInfoList[i].depth = -1;
1120 boards[i][EP_STATUS] = EP_NONE;
1121 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1127 /* [AS] Adjudication threshold */
1128 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1130 InitEngine(&first, 0);
1131 InitEngine(&second, 1);
1134 pairing.which = "pairing"; // pairing engine
1135 pairing.pr = NoProc;
1137 pairing.program = appData.pairingEngine;
1138 pairing.host = "localhost";
1141 if (appData.icsActive) {
1142 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1143 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1144 appData.clockMode = FALSE;
1145 first.sendTime = second.sendTime = 0;
1149 /* Override some settings from environment variables, for backward
1150 compatibility. Unfortunately it's not feasible to have the env
1151 vars just set defaults, at least in xboard. Ugh.
1153 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1158 if (!appData.icsActive) {
1162 /* Check for variants that are supported only in ICS mode,
1163 or not at all. Some that are accepted here nevertheless
1164 have bugs; see comments below.
1166 VariantClass variant = StringToVariant(appData.variant);
1168 case VariantBughouse: /* need four players and two boards */
1169 case VariantKriegspiel: /* need to hide pieces and move details */
1170 /* case VariantFischeRandom: (Fabien: moved below) */
1171 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1172 if( (len >= MSG_SIZ) && appData.debugMode )
1173 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1175 DisplayFatalError(buf, 0, 2);
1178 case VariantUnknown:
1179 case VariantLoadable:
1189 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1190 if( (len >= MSG_SIZ) && appData.debugMode )
1191 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1193 DisplayFatalError(buf, 0, 2);
1196 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1197 case VariantFairy: /* [HGM] TestLegality definitely off! */
1198 case VariantGothic: /* [HGM] should work */
1199 case VariantCapablanca: /* [HGM] should work */
1200 case VariantCourier: /* [HGM] initial forced moves not implemented */
1201 case VariantShogi: /* [HGM] could still mate with pawn drop */
1202 case VariantChu: /* [HGM] experimental */
1203 case VariantKnightmate: /* [HGM] should work */
1204 case VariantCylinder: /* [HGM] untested */
1205 case VariantFalcon: /* [HGM] untested */
1206 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1207 offboard interposition not understood */
1208 case VariantNormal: /* definitely works! */
1209 case VariantWildCastle: /* pieces not automatically shuffled */
1210 case VariantNoCastle: /* pieces not automatically shuffled */
1211 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1212 case VariantLosers: /* should work except for win condition,
1213 and doesn't know captures are mandatory */
1214 case VariantSuicide: /* should work except for win condition,
1215 and doesn't know captures are mandatory */
1216 case VariantGiveaway: /* should work except for win condition,
1217 and doesn't know captures are mandatory */
1218 case VariantTwoKings: /* should work */
1219 case VariantAtomic: /* should work except for win condition */
1220 case Variant3Check: /* should work except for win condition */
1221 case VariantShatranj: /* should work except for all win conditions */
1222 case VariantMakruk: /* should work except for draw countdown */
1223 case VariantASEAN : /* should work except for draw countdown */
1224 case VariantBerolina: /* might work if TestLegality is off */
1225 case VariantCapaRandom: /* should work */
1226 case VariantJanus: /* should work */
1227 case VariantSuper: /* experimental */
1228 case VariantGreat: /* experimental, requires legality testing to be off */
1229 case VariantSChess: /* S-Chess, should work */
1230 case VariantGrand: /* should work */
1231 case VariantSpartan: /* should work */
1232 case VariantLion: /* should work */
1233 case VariantChuChess: /* should work */
1241 NextIntegerFromString (char ** str, long * value)
1246 while( *s == ' ' || *s == '\t' ) {
1252 if( *s >= '0' && *s <= '9' ) {
1253 while( *s >= '0' && *s <= '9' ) {
1254 *value = *value * 10 + (*s - '0');
1267 NextTimeControlFromString (char ** str, long * value)
1270 int result = NextIntegerFromString( str, &temp );
1273 *value = temp * 60; /* Minutes */
1274 if( **str == ':' ) {
1276 result = NextIntegerFromString( str, &temp );
1277 *value += temp; /* Seconds */
1285 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1286 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1287 int result = -1, type = 0; long temp, temp2;
1289 if(**str != ':') return -1; // old params remain in force!
1291 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1292 if( NextIntegerFromString( str, &temp ) ) return -1;
1293 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1296 /* time only: incremental or sudden-death time control */
1297 if(**str == '+') { /* increment follows; read it */
1299 if(**str == '!') type = *(*str)++; // Bronstein TC
1300 if(result = NextIntegerFromString( str, &temp2)) return -1;
1301 *inc = temp2 * 1000;
1302 if(**str == '.') { // read fraction of increment
1303 char *start = ++(*str);
1304 if(result = NextIntegerFromString( str, &temp2)) return -1;
1306 while(start++ < *str) temp2 /= 10;
1310 *moves = 0; *tc = temp * 1000; *incType = type;
1314 (*str)++; /* classical time control */
1315 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1327 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1328 { /* [HGM] get time to add from the multi-session time-control string */
1329 int incType, moves=1; /* kludge to force reading of first session */
1330 long time, increment;
1333 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1335 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1336 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1337 if(movenr == -1) return time; /* last move before new session */
1338 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1339 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1340 if(!moves) return increment; /* current session is incremental */
1341 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1342 } while(movenr >= -1); /* try again for next session */
1344 return 0; // no new time quota on this move
1348 ParseTimeControl (char *tc, float ti, int mps)
1352 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1355 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1356 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1357 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1361 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1363 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1366 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1368 snprintf(buf, MSG_SIZ, ":%s", mytc);
1370 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1372 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1377 /* Parse second time control */
1380 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1388 timeControl_2 = tc2 * 1000;
1398 timeControl = tc1 * 1000;
1401 timeIncrement = ti * 1000; /* convert to ms */
1402 movesPerSession = 0;
1405 movesPerSession = mps;
1413 if (appData.debugMode) {
1414 # ifdef __GIT_VERSION
1415 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1417 fprintf(debugFP, "Version: %s\n", programVersion);
1420 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1422 set_cont_sequence(appData.wrapContSeq);
1423 if (appData.matchGames > 0) {
1424 appData.matchMode = TRUE;
1425 } else if (appData.matchMode) {
1426 appData.matchGames = 1;
1428 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1429 appData.matchGames = appData.sameColorGames;
1430 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1431 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1432 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1435 if (appData.noChessProgram || first.protocolVersion == 1) {
1438 /* kludge: allow timeout for initial "feature" commands */
1440 DisplayMessage("", _("Starting chess program"));
1441 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1446 CalculateIndex (int index, int gameNr)
1447 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1449 if(index > 0) return index; // fixed nmber
1450 if(index == 0) return 1;
1451 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1452 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1457 LoadGameOrPosition (int gameNr)
1458 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1459 if (*appData.loadGameFile != NULLCHAR) {
1460 if (!LoadGameFromFile(appData.loadGameFile,
1461 CalculateIndex(appData.loadGameIndex, gameNr),
1462 appData.loadGameFile, FALSE)) {
1463 DisplayFatalError(_("Bad game file"), 0, 1);
1466 } else if (*appData.loadPositionFile != NULLCHAR) {
1467 if (!LoadPositionFromFile(appData.loadPositionFile,
1468 CalculateIndex(appData.loadPositionIndex, gameNr),
1469 appData.loadPositionFile)) {
1470 DisplayFatalError(_("Bad position file"), 0, 1);
1478 ReserveGame (int gameNr, char resChar)
1480 FILE *tf = fopen(appData.tourneyFile, "r+");
1481 char *p, *q, c, buf[MSG_SIZ];
1482 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1483 safeStrCpy(buf, lastMsg, MSG_SIZ);
1484 DisplayMessage(_("Pick new game"), "");
1485 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1486 ParseArgsFromFile(tf);
1487 p = q = appData.results;
1488 if(appData.debugMode) {
1489 char *r = appData.participants;
1490 fprintf(debugFP, "results = '%s'\n", p);
1491 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1492 fprintf(debugFP, "\n");
1494 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1496 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1497 safeStrCpy(q, p, strlen(p) + 2);
1498 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1499 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1500 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1501 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1504 fseek(tf, -(strlen(p)+4), SEEK_END);
1506 if(c != '"') // depending on DOS or Unix line endings we can be one off
1507 fseek(tf, -(strlen(p)+2), SEEK_END);
1508 else fseek(tf, -(strlen(p)+3), SEEK_END);
1509 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1510 DisplayMessage(buf, "");
1511 free(p); appData.results = q;
1512 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1513 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1514 int round = appData.defaultMatchGames * appData.tourneyType;
1515 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1516 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1517 UnloadEngine(&first); // next game belongs to other pairing;
1518 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1520 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1524 MatchEvent (int mode)
1525 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1527 if(matchMode) { // already in match mode: switch it off
1529 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1532 // if(gameMode != BeginningOfGame) {
1533 // DisplayError(_("You can only start a match from the initial position."), 0);
1537 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1538 /* Set up machine vs. machine match */
1540 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1541 if(appData.tourneyFile[0]) {
1543 if(nextGame > appData.matchGames) {
1545 if(strchr(appData.results, '*') == NULL) {
1547 appData.tourneyCycles++;
1548 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1550 NextTourneyGame(-1, &dummy);
1552 if(nextGame <= appData.matchGames) {
1553 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1555 ScheduleDelayedEvent(NextMatchGame, 10000);
1560 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1561 DisplayError(buf, 0);
1562 appData.tourneyFile[0] = 0;
1566 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1567 DisplayFatalError(_("Can't have a match with no chess programs"),
1572 matchGame = roundNr = 1;
1573 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1577 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1580 InitBackEnd3 P((void))
1582 GameMode initialMode;
1586 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1587 !strcmp(appData.variant, "normal") && // no explicit variant request
1588 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1589 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1590 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1591 char c, *q = first.variants, *p = strchr(q, ',');
1592 if(p) *p = NULLCHAR;
1593 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1595 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1596 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1597 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1598 Reset(TRUE, FALSE); // and re-initialize
1603 InitChessProgram(&first, startedFromSetupPosition);
1605 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1606 free(programVersion);
1607 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1608 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1609 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1612 if (appData.icsActive) {
1614 /* [DM] Make a console window if needed [HGM] merged ifs */
1620 if (*appData.icsCommPort != NULLCHAR)
1621 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1622 appData.icsCommPort);
1624 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1625 appData.icsHost, appData.icsPort);
1627 if( (len >= MSG_SIZ) && appData.debugMode )
1628 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630 DisplayFatalError(buf, err, 1);
1635 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1637 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1638 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1639 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1640 } else if (appData.noChessProgram) {
1646 if (*appData.cmailGameName != NULLCHAR) {
1648 OpenLoopback(&cmailPR);
1650 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1654 DisplayMessage("", "");
1655 if (StrCaseCmp(appData.initialMode, "") == 0) {
1656 initialMode = BeginningOfGame;
1657 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1658 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1659 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1660 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1663 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1664 initialMode = TwoMachinesPlay;
1665 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1666 initialMode = AnalyzeFile;
1667 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1668 initialMode = AnalyzeMode;
1669 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1670 initialMode = MachinePlaysWhite;
1671 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1672 initialMode = MachinePlaysBlack;
1673 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1674 initialMode = EditGame;
1675 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1676 initialMode = EditPosition;
1677 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1678 initialMode = Training;
1680 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1681 if( (len >= MSG_SIZ) && appData.debugMode )
1682 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1684 DisplayFatalError(buf, 0, 2);
1688 if (appData.matchMode) {
1689 if(appData.tourneyFile[0]) { // start tourney from command line
1691 if(f = fopen(appData.tourneyFile, "r")) {
1692 ParseArgsFromFile(f); // make sure tourney parmeters re known
1694 appData.clockMode = TRUE;
1696 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1699 } else if (*appData.cmailGameName != NULLCHAR) {
1700 /* Set up cmail mode */
1701 ReloadCmailMsgEvent(TRUE);
1703 /* Set up other modes */
1704 if (initialMode == AnalyzeFile) {
1705 if (*appData.loadGameFile == NULLCHAR) {
1706 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1710 if (*appData.loadGameFile != NULLCHAR) {
1711 (void) LoadGameFromFile(appData.loadGameFile,
1712 appData.loadGameIndex,
1713 appData.loadGameFile, TRUE);
1714 } else if (*appData.loadPositionFile != NULLCHAR) {
1715 (void) LoadPositionFromFile(appData.loadPositionFile,
1716 appData.loadPositionIndex,
1717 appData.loadPositionFile);
1718 /* [HGM] try to make self-starting even after FEN load */
1719 /* to allow automatic setup of fairy variants with wtm */
1720 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1721 gameMode = BeginningOfGame;
1722 setboardSpoiledMachineBlack = 1;
1724 /* [HGM] loadPos: make that every new game uses the setup */
1725 /* from file as long as we do not switch variant */
1726 if(!blackPlaysFirst) {
1727 startedFromPositionFile = TRUE;
1728 CopyBoard(filePosition, boards[0]);
1731 if (initialMode == AnalyzeMode) {
1732 if (appData.noChessProgram) {
1733 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1736 if (appData.icsActive) {
1737 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1741 } else if (initialMode == AnalyzeFile) {
1742 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1743 ShowThinkingEvent();
1745 AnalysisPeriodicEvent(1);
1746 } else if (initialMode == MachinePlaysWhite) {
1747 if (appData.noChessProgram) {
1748 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1752 if (appData.icsActive) {
1753 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1757 MachineWhiteEvent();
1758 } else if (initialMode == MachinePlaysBlack) {
1759 if (appData.noChessProgram) {
1760 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1764 if (appData.icsActive) {
1765 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1769 MachineBlackEvent();
1770 } else if (initialMode == TwoMachinesPlay) {
1771 if (appData.noChessProgram) {
1772 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1776 if (appData.icsActive) {
1777 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1782 } else if (initialMode == EditGame) {
1784 } else if (initialMode == EditPosition) {
1785 EditPositionEvent();
1786 } else if (initialMode == Training) {
1787 if (*appData.loadGameFile == NULLCHAR) {
1788 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1797 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1799 DisplayBook(current+1);
1801 MoveHistorySet( movelist, first, last, current, pvInfoList );
1803 EvalGraphSet( first, last, current, pvInfoList );
1805 MakeEngineOutputTitle();
1809 * Establish will establish a contact to a remote host.port.
1810 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1811 * used to talk to the host.
1812 * Returns 0 if okay, error code if not.
1819 if (*appData.icsCommPort != NULLCHAR) {
1820 /* Talk to the host through a serial comm port */
1821 return OpenCommPort(appData.icsCommPort, &icsPR);
1823 } else if (*appData.gateway != NULLCHAR) {
1824 if (*appData.remoteShell == NULLCHAR) {
1825 /* Use the rcmd protocol to run telnet program on a gateway host */
1826 snprintf(buf, sizeof(buf), "%s %s %s",
1827 appData.telnetProgram, appData.icsHost, appData.icsPort);
1828 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1831 /* Use the rsh program to run telnet program on a gateway host */
1832 if (*appData.remoteUser == NULLCHAR) {
1833 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1834 appData.gateway, appData.telnetProgram,
1835 appData.icsHost, appData.icsPort);
1837 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1838 appData.remoteShell, appData.gateway,
1839 appData.remoteUser, appData.telnetProgram,
1840 appData.icsHost, appData.icsPort);
1842 return StartChildProcess(buf, "", &icsPR);
1845 } else if (appData.useTelnet) {
1846 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1849 /* TCP socket interface differs somewhat between
1850 Unix and NT; handle details in the front end.
1852 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1857 EscapeExpand (char *p, char *q)
1858 { // [HGM] initstring: routine to shape up string arguments
1859 while(*p++ = *q++) if(p[-1] == '\\')
1861 case 'n': p[-1] = '\n'; break;
1862 case 'r': p[-1] = '\r'; break;
1863 case 't': p[-1] = '\t'; break;
1864 case '\\': p[-1] = '\\'; break;
1865 case 0: *p = 0; return;
1866 default: p[-1] = q[-1]; break;
1871 show_bytes (FILE *fp, char *buf, int count)
1874 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1875 fprintf(fp, "\\%03o", *buf & 0xff);
1884 /* Returns an errno value */
1886 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1888 char buf[8192], *p, *q, *buflim;
1889 int left, newcount, outcount;
1891 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1892 *appData.gateway != NULLCHAR) {
1893 if (appData.debugMode) {
1894 fprintf(debugFP, ">ICS: ");
1895 show_bytes(debugFP, message, count);
1896 fprintf(debugFP, "\n");
1898 return OutputToProcess(pr, message, count, outError);
1901 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1908 if (appData.debugMode) {
1909 fprintf(debugFP, ">ICS: ");
1910 show_bytes(debugFP, buf, newcount);
1911 fprintf(debugFP, "\n");
1913 outcount = OutputToProcess(pr, buf, newcount, outError);
1914 if (outcount < newcount) return -1; /* to be sure */
1921 } else if (((unsigned char) *p) == TN_IAC) {
1922 *q++ = (char) TN_IAC;
1929 if (appData.debugMode) {
1930 fprintf(debugFP, ">ICS: ");
1931 show_bytes(debugFP, buf, newcount);
1932 fprintf(debugFP, "\n");
1934 outcount = OutputToProcess(pr, buf, newcount, outError);
1935 if (outcount < newcount) return -1; /* to be sure */
1940 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1942 int outError, outCount;
1943 static int gotEof = 0;
1946 /* Pass data read from player on to ICS */
1949 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1950 if (outCount < count) {
1951 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1953 if(have_sent_ICS_logon == 2) {
1954 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1955 fprintf(ini, "%s", message);
1956 have_sent_ICS_logon = 3;
1958 have_sent_ICS_logon = 1;
1959 } else if(have_sent_ICS_logon == 3) {
1960 fprintf(ini, "%s", message);
1962 have_sent_ICS_logon = 1;
1964 } else if (count < 0) {
1965 RemoveInputSource(isr);
1966 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1967 } else if (gotEof++ > 0) {
1968 RemoveInputSource(isr);
1969 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1975 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1976 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1977 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1978 SendToICS("date\n");
1979 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1982 /* added routine for printf style output to ics */
1984 ics_printf (char *format, ...)
1986 char buffer[MSG_SIZ];
1989 va_start(args, format);
1990 vsnprintf(buffer, sizeof(buffer), format, args);
1991 buffer[sizeof(buffer)-1] = '\0';
1999 int count, outCount, outError;
2001 if (icsPR == NoProc) return;
2004 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2005 if (outCount < count) {
2006 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2010 /* This is used for sending logon scripts to the ICS. Sending
2011 without a delay causes problems when using timestamp on ICC
2012 (at least on my machine). */
2014 SendToICSDelayed (char *s, long msdelay)
2016 int count, outCount, outError;
2018 if (icsPR == NoProc) return;
2021 if (appData.debugMode) {
2022 fprintf(debugFP, ">ICS: ");
2023 show_bytes(debugFP, s, count);
2024 fprintf(debugFP, "\n");
2026 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2028 if (outCount < count) {
2029 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2034 /* Remove all highlighting escape sequences in s
2035 Also deletes any suffix starting with '('
2038 StripHighlightAndTitle (char *s)
2040 static char retbuf[MSG_SIZ];
2043 while (*s != NULLCHAR) {
2044 while (*s == '\033') {
2045 while (*s != NULLCHAR && !isalpha(*s)) s++;
2046 if (*s != NULLCHAR) s++;
2048 while (*s != NULLCHAR && *s != '\033') {
2049 if (*s == '(' || *s == '[') {
2060 /* Remove all highlighting escape sequences in s */
2062 StripHighlight (char *s)
2064 static char retbuf[MSG_SIZ];
2067 while (*s != NULLCHAR) {
2068 while (*s == '\033') {
2069 while (*s != NULLCHAR && !isalpha(*s)) s++;
2070 if (*s != NULLCHAR) s++;
2072 while (*s != NULLCHAR && *s != '\033') {
2080 char engineVariant[MSG_SIZ];
2081 char *variantNames[] = VARIANT_NAMES;
2083 VariantName (VariantClass v)
2085 if(v == VariantUnknown || *engineVariant) return engineVariant;
2086 return variantNames[v];
2090 /* Identify a variant from the strings the chess servers use or the
2091 PGN Variant tag names we use. */
2093 StringToVariant (char *e)
2097 VariantClass v = VariantNormal;
2098 int i, found = FALSE;
2104 /* [HGM] skip over optional board-size prefixes */
2105 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2106 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2107 while( *e++ != '_');
2110 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2114 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2115 if (p = StrCaseStr(e, variantNames[i])) {
2116 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2117 v = (VariantClass) i;
2124 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2125 || StrCaseStr(e, "wild/fr")
2126 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2127 v = VariantFischeRandom;
2128 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2129 (i = 1, p = StrCaseStr(e, "w"))) {
2131 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2138 case 0: /* FICS only, actually */
2140 /* Castling legal even if K starts on d-file */
2141 v = VariantWildCastle;
2146 /* Castling illegal even if K & R happen to start in
2147 normal positions. */
2148 v = VariantNoCastle;
2161 /* Castling legal iff K & R start in normal positions */
2167 /* Special wilds for position setup; unclear what to do here */
2168 v = VariantLoadable;
2171 /* Bizarre ICC game */
2172 v = VariantTwoKings;
2175 v = VariantKriegspiel;
2181 v = VariantFischeRandom;
2184 v = VariantCrazyhouse;
2187 v = VariantBughouse;
2193 /* Not quite the same as FICS suicide! */
2194 v = VariantGiveaway;
2200 v = VariantShatranj;
2203 /* Temporary names for future ICC types. The name *will* change in
2204 the next xboard/WinBoard release after ICC defines it. */
2242 v = VariantCapablanca;
2245 v = VariantKnightmate;
2251 v = VariantCylinder;
2257 v = VariantCapaRandom;
2260 v = VariantBerolina;
2272 /* Found "wild" or "w" in the string but no number;
2273 must assume it's normal chess. */
2277 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2278 if( (len >= MSG_SIZ) && appData.debugMode )
2279 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2281 DisplayError(buf, 0);
2287 if (appData.debugMode) {
2288 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2289 e, wnum, VariantName(v));
2294 static int leftover_start = 0, leftover_len = 0;
2295 char star_match[STAR_MATCH_N][MSG_SIZ];
2297 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2298 advance *index beyond it, and set leftover_start to the new value of
2299 *index; else return FALSE. If pattern contains the character '*', it
2300 matches any sequence of characters not containing '\r', '\n', or the
2301 character following the '*' (if any), and the matched sequence(s) are
2302 copied into star_match.
2305 looking_at ( char *buf, int *index, char *pattern)
2307 char *bufp = &buf[*index], *patternp = pattern;
2309 char *matchp = star_match[0];
2312 if (*patternp == NULLCHAR) {
2313 *index = leftover_start = bufp - buf;
2317 if (*bufp == NULLCHAR) return FALSE;
2318 if (*patternp == '*') {
2319 if (*bufp == *(patternp + 1)) {
2321 matchp = star_match[++star_count];
2325 } else if (*bufp == '\n' || *bufp == '\r') {
2327 if (*patternp == NULLCHAR)
2332 *matchp++ = *bufp++;
2336 if (*patternp != *bufp) return FALSE;
2343 SendToPlayer (char *data, int length)
2345 int error, outCount;
2346 outCount = OutputToProcess(NoProc, data, length, &error);
2347 if (outCount < length) {
2348 DisplayFatalError(_("Error writing to display"), error, 1);
2353 PackHolding (char packed[], char *holding)
2363 switch (runlength) {
2374 sprintf(q, "%d", runlength);
2386 /* Telnet protocol requests from the front end */
2388 TelnetRequest (unsigned char ddww, unsigned char option)
2390 unsigned char msg[3];
2391 int outCount, outError;
2393 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2395 if (appData.debugMode) {
2396 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2421 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2424 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2429 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2431 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2438 if (!appData.icsActive) return;
2439 TelnetRequest(TN_DO, TN_ECHO);
2445 if (!appData.icsActive) return;
2446 TelnetRequest(TN_DONT, TN_ECHO);
2450 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2452 /* put the holdings sent to us by the server on the board holdings area */
2453 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2457 if(gameInfo.holdingsWidth < 2) return;
2458 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2459 return; // prevent overwriting by pre-board holdings
2461 if( (int)lowestPiece >= BlackPawn ) {
2464 holdingsStartRow = BOARD_HEIGHT-1;
2467 holdingsColumn = BOARD_WIDTH-1;
2468 countsColumn = BOARD_WIDTH-2;
2469 holdingsStartRow = 0;
2473 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2474 board[i][holdingsColumn] = EmptySquare;
2475 board[i][countsColumn] = (ChessSquare) 0;
2477 while( (p=*holdings++) != NULLCHAR ) {
2478 piece = CharToPiece( ToUpper(p) );
2479 if(piece == EmptySquare) continue;
2480 /*j = (int) piece - (int) WhitePawn;*/
2481 j = PieceToNumber(piece);
2482 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2483 if(j < 0) continue; /* should not happen */
2484 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2485 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2486 board[holdingsStartRow+j*direction][countsColumn]++;
2492 VariantSwitch (Board board, VariantClass newVariant)
2494 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2495 static Board oldBoard;
2497 startedFromPositionFile = FALSE;
2498 if(gameInfo.variant == newVariant) return;
2500 /* [HGM] This routine is called each time an assignment is made to
2501 * gameInfo.variant during a game, to make sure the board sizes
2502 * are set to match the new variant. If that means adding or deleting
2503 * holdings, we shift the playing board accordingly
2504 * This kludge is needed because in ICS observe mode, we get boards
2505 * of an ongoing game without knowing the variant, and learn about the
2506 * latter only later. This can be because of the move list we requested,
2507 * in which case the game history is refilled from the beginning anyway,
2508 * but also when receiving holdings of a crazyhouse game. In the latter
2509 * case we want to add those holdings to the already received position.
2513 if (appData.debugMode) {
2514 fprintf(debugFP, "Switch board from %s to %s\n",
2515 VariantName(gameInfo.variant), VariantName(newVariant));
2516 setbuf(debugFP, NULL);
2518 shuffleOpenings = 0; /* [HGM] shuffle */
2519 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2523 newWidth = 9; newHeight = 9;
2524 gameInfo.holdingsSize = 7;
2525 case VariantBughouse:
2526 case VariantCrazyhouse:
2527 newHoldingsWidth = 2; break;
2531 newHoldingsWidth = 2;
2532 gameInfo.holdingsSize = 8;
2535 case VariantCapablanca:
2536 case VariantCapaRandom:
2539 newHoldingsWidth = gameInfo.holdingsSize = 0;
2542 if(newWidth != gameInfo.boardWidth ||
2543 newHeight != gameInfo.boardHeight ||
2544 newHoldingsWidth != gameInfo.holdingsWidth ) {
2546 /* shift position to new playing area, if needed */
2547 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2548 for(i=0; i<BOARD_HEIGHT; i++)
2549 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2550 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2552 for(i=0; i<newHeight; i++) {
2553 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2554 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2556 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2557 for(i=0; i<BOARD_HEIGHT; i++)
2558 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2559 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2562 board[HOLDINGS_SET] = 0;
2563 gameInfo.boardWidth = newWidth;
2564 gameInfo.boardHeight = newHeight;
2565 gameInfo.holdingsWidth = newHoldingsWidth;
2566 gameInfo.variant = newVariant;
2567 InitDrawingSizes(-2, 0);
2568 } else gameInfo.variant = newVariant;
2569 CopyBoard(oldBoard, board); // remember correctly formatted board
2570 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2571 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2574 static int loggedOn = FALSE;
2576 /*-- Game start info cache: --*/
2578 char gs_kind[MSG_SIZ];
2579 static char player1Name[128] = "";
2580 static char player2Name[128] = "";
2581 static char cont_seq[] = "\n\\ ";
2582 static int player1Rating = -1;
2583 static int player2Rating = -1;
2584 /*----------------------------*/
2586 ColorClass curColor = ColorNormal;
2587 int suppressKibitz = 0;
2590 Boolean soughtPending = FALSE;
2591 Boolean seekGraphUp;
2592 #define MAX_SEEK_ADS 200
2594 char *seekAdList[MAX_SEEK_ADS];
2595 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2596 float tcList[MAX_SEEK_ADS];
2597 char colorList[MAX_SEEK_ADS];
2598 int nrOfSeekAds = 0;
2599 int minRating = 1010, maxRating = 2800;
2600 int hMargin = 10, vMargin = 20, h, w;
2601 extern int squareSize, lineGap;
2606 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2607 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2608 if(r < minRating+100 && r >=0 ) r = minRating+100;
2609 if(r > maxRating) r = maxRating;
2610 if(tc < 1.f) tc = 1.f;
2611 if(tc > 95.f) tc = 95.f;
2612 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2613 y = ((double)r - minRating)/(maxRating - minRating)
2614 * (h-vMargin-squareSize/8-1) + vMargin;
2615 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2616 if(strstr(seekAdList[i], " u ")) color = 1;
2617 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2618 !strstr(seekAdList[i], "bullet") &&
2619 !strstr(seekAdList[i], "blitz") &&
2620 !strstr(seekAdList[i], "standard") ) color = 2;
2621 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2622 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2626 PlotSingleSeekAd (int i)
2632 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2634 char buf[MSG_SIZ], *ext = "";
2635 VariantClass v = StringToVariant(type);
2636 if(strstr(type, "wild")) {
2637 ext = type + 4; // append wild number
2638 if(v == VariantFischeRandom) type = "chess960"; else
2639 if(v == VariantLoadable) type = "setup"; else
2640 type = VariantName(v);
2642 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2643 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2644 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2645 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2646 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2647 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2648 seekNrList[nrOfSeekAds] = nr;
2649 zList[nrOfSeekAds] = 0;
2650 seekAdList[nrOfSeekAds++] = StrSave(buf);
2651 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2656 EraseSeekDot (int i)
2658 int x = xList[i], y = yList[i], d=squareSize/4, k;
2659 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2660 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2661 // now replot every dot that overlapped
2662 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2663 int xx = xList[k], yy = yList[k];
2664 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2665 DrawSeekDot(xx, yy, colorList[k]);
2670 RemoveSeekAd (int nr)
2673 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2675 if(seekAdList[i]) free(seekAdList[i]);
2676 seekAdList[i] = seekAdList[--nrOfSeekAds];
2677 seekNrList[i] = seekNrList[nrOfSeekAds];
2678 ratingList[i] = ratingList[nrOfSeekAds];
2679 colorList[i] = colorList[nrOfSeekAds];
2680 tcList[i] = tcList[nrOfSeekAds];
2681 xList[i] = xList[nrOfSeekAds];
2682 yList[i] = yList[nrOfSeekAds];
2683 zList[i] = zList[nrOfSeekAds];
2684 seekAdList[nrOfSeekAds] = NULL;
2690 MatchSoughtLine (char *line)
2692 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2693 int nr, base, inc, u=0; char dummy;
2695 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2696 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2698 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2699 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2700 // match: compact and save the line
2701 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2711 if(!seekGraphUp) return FALSE;
2712 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2713 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2715 DrawSeekBackground(0, 0, w, h);
2716 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2717 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2718 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2719 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2721 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2724 snprintf(buf, MSG_SIZ, "%d", i);
2725 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2728 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2729 for(i=1; i<100; i+=(i<10?1:5)) {
2730 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2731 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2732 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2734 snprintf(buf, MSG_SIZ, "%d", i);
2735 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2738 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2743 SeekGraphClick (ClickType click, int x, int y, int moving)
2745 static int lastDown = 0, displayed = 0, lastSecond;
2746 if(y < 0) return FALSE;
2747 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2748 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2749 if(!seekGraphUp) return FALSE;
2750 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2751 DrawPosition(TRUE, NULL);
2754 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2755 if(click == Release || moving) return FALSE;
2757 soughtPending = TRUE;
2758 SendToICS(ics_prefix);
2759 SendToICS("sought\n"); // should this be "sought all"?
2760 } else { // issue challenge based on clicked ad
2761 int dist = 10000; int i, closest = 0, second = 0;
2762 for(i=0; i<nrOfSeekAds; i++) {
2763 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2764 if(d < dist) { dist = d; closest = i; }
2765 second += (d - zList[i] < 120); // count in-range ads
2766 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2770 second = (second > 1);
2771 if(displayed != closest || second != lastSecond) {
2772 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2773 lastSecond = second; displayed = closest;
2775 if(click == Press) {
2776 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2779 } // on press 'hit', only show info
2780 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2781 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2782 SendToICS(ics_prefix);
2784 return TRUE; // let incoming board of started game pop down the graph
2785 } else if(click == Release) { // release 'miss' is ignored
2786 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2787 if(moving == 2) { // right up-click
2788 nrOfSeekAds = 0; // refresh graph
2789 soughtPending = TRUE;
2790 SendToICS(ics_prefix);
2791 SendToICS("sought\n"); // should this be "sought all"?
2794 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2795 // press miss or release hit 'pop down' seek graph
2796 seekGraphUp = FALSE;
2797 DrawPosition(TRUE, NULL);
2803 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2805 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2806 #define STARTED_NONE 0
2807 #define STARTED_MOVES 1
2808 #define STARTED_BOARD 2
2809 #define STARTED_OBSERVE 3
2810 #define STARTED_HOLDINGS 4
2811 #define STARTED_CHATTER 5
2812 #define STARTED_COMMENT 6
2813 #define STARTED_MOVES_NOHIDE 7
2815 static int started = STARTED_NONE;
2816 static char parse[20000];
2817 static int parse_pos = 0;
2818 static char buf[BUF_SIZE + 1];
2819 static int firstTime = TRUE, intfSet = FALSE;
2820 static ColorClass prevColor = ColorNormal;
2821 static int savingComment = FALSE;
2822 static int cmatch = 0; // continuation sequence match
2829 int backup; /* [DM] For zippy color lines */
2831 char talker[MSG_SIZ]; // [HGM] chat
2834 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2836 if (appData.debugMode) {
2838 fprintf(debugFP, "<ICS: ");
2839 show_bytes(debugFP, data, count);
2840 fprintf(debugFP, "\n");
2844 if (appData.debugMode) { int f = forwardMostMove;
2845 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2846 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2847 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2850 /* If last read ended with a partial line that we couldn't parse,
2851 prepend it to the new read and try again. */
2852 if (leftover_len > 0) {
2853 for (i=0; i<leftover_len; i++)
2854 buf[i] = buf[leftover_start + i];
2857 /* copy new characters into the buffer */
2858 bp = buf + leftover_len;
2859 buf_len=leftover_len;
2860 for (i=0; i<count; i++)
2863 if (data[i] == '\r')
2866 // join lines split by ICS?
2867 if (!appData.noJoin)
2870 Joining just consists of finding matches against the
2871 continuation sequence, and discarding that sequence
2872 if found instead of copying it. So, until a match
2873 fails, there's nothing to do since it might be the
2874 complete sequence, and thus, something we don't want
2877 if (data[i] == cont_seq[cmatch])
2880 if (cmatch == strlen(cont_seq))
2882 cmatch = 0; // complete match. just reset the counter
2885 it's possible for the ICS to not include the space
2886 at the end of the last word, making our [correct]
2887 join operation fuse two separate words. the server
2888 does this when the space occurs at the width setting.
2890 if (!buf_len || buf[buf_len-1] != ' ')
2901 match failed, so we have to copy what matched before
2902 falling through and copying this character. In reality,
2903 this will only ever be just the newline character, but
2904 it doesn't hurt to be precise.
2906 strncpy(bp, cont_seq, cmatch);
2918 buf[buf_len] = NULLCHAR;
2919 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2924 while (i < buf_len) {
2925 /* Deal with part of the TELNET option negotiation
2926 protocol. We refuse to do anything beyond the
2927 defaults, except that we allow the WILL ECHO option,
2928 which ICS uses to turn off password echoing when we are
2929 directly connected to it. We reject this option
2930 if localLineEditing mode is on (always on in xboard)
2931 and we are talking to port 23, which might be a real
2932 telnet server that will try to keep WILL ECHO on permanently.
2934 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2935 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2936 unsigned char option;
2938 switch ((unsigned char) buf[++i]) {
2940 if (appData.debugMode)
2941 fprintf(debugFP, "\n<WILL ");
2942 switch (option = (unsigned char) buf[++i]) {
2944 if (appData.debugMode)
2945 fprintf(debugFP, "ECHO ");
2946 /* Reply only if this is a change, according
2947 to the protocol rules. */
2948 if (remoteEchoOption) break;
2949 if (appData.localLineEditing &&
2950 atoi(appData.icsPort) == TN_PORT) {
2951 TelnetRequest(TN_DONT, TN_ECHO);
2954 TelnetRequest(TN_DO, TN_ECHO);
2955 remoteEchoOption = TRUE;
2959 if (appData.debugMode)
2960 fprintf(debugFP, "%d ", option);
2961 /* Whatever this is, we don't want it. */
2962 TelnetRequest(TN_DONT, option);
2967 if (appData.debugMode)
2968 fprintf(debugFP, "\n<WONT ");
2969 switch (option = (unsigned char) buf[++i]) {
2971 if (appData.debugMode)
2972 fprintf(debugFP, "ECHO ");
2973 /* Reply only if this is a change, according
2974 to the protocol rules. */
2975 if (!remoteEchoOption) break;
2977 TelnetRequest(TN_DONT, TN_ECHO);
2978 remoteEchoOption = FALSE;
2981 if (appData.debugMode)
2982 fprintf(debugFP, "%d ", (unsigned char) option);
2983 /* Whatever this is, it must already be turned
2984 off, because we never agree to turn on
2985 anything non-default, so according to the
2986 protocol rules, we don't reply. */
2991 if (appData.debugMode)
2992 fprintf(debugFP, "\n<DO ");
2993 switch (option = (unsigned char) buf[++i]) {
2995 /* Whatever this is, we refuse to do it. */
2996 if (appData.debugMode)
2997 fprintf(debugFP, "%d ", option);
2998 TelnetRequest(TN_WONT, option);
3003 if (appData.debugMode)
3004 fprintf(debugFP, "\n<DONT ");
3005 switch (option = (unsigned char) buf[++i]) {
3007 if (appData.debugMode)
3008 fprintf(debugFP, "%d ", option);
3009 /* Whatever this is, we are already not doing
3010 it, because we never agree to do anything
3011 non-default, so according to the protocol
3012 rules, we don't reply. */
3017 if (appData.debugMode)
3018 fprintf(debugFP, "\n<IAC ");
3019 /* Doubled IAC; pass it through */
3023 if (appData.debugMode)
3024 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3025 /* Drop all other telnet commands on the floor */
3028 if (oldi > next_out)
3029 SendToPlayer(&buf[next_out], oldi - next_out);
3035 /* OK, this at least will *usually* work */
3036 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3040 if (loggedOn && !intfSet) {
3041 if (ics_type == ICS_ICC) {
3042 snprintf(str, MSG_SIZ,
3043 "/set-quietly interface %s\n/set-quietly style 12\n",
3045 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3046 strcat(str, "/set-2 51 1\n/set seek 1\n");
3047 } else if (ics_type == ICS_CHESSNET) {
3048 snprintf(str, MSG_SIZ, "/style 12\n");
3050 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3051 strcat(str, programVersion);
3052 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3053 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3054 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3056 strcat(str, "$iset nohighlight 1\n");
3058 strcat(str, "$iset lock 1\n$style 12\n");
3061 NotifyFrontendLogin();
3065 if (started == STARTED_COMMENT) {
3066 /* Accumulate characters in comment */
3067 parse[parse_pos++] = buf[i];
3068 if (buf[i] == '\n') {
3069 parse[parse_pos] = NULLCHAR;
3070 if(chattingPartner>=0) {
3072 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3073 OutputChatMessage(chattingPartner, mess);
3074 chattingPartner = -1;
3075 next_out = i+1; // [HGM] suppress printing in ICS window
3077 if(!suppressKibitz) // [HGM] kibitz
3078 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3079 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3080 int nrDigit = 0, nrAlph = 0, j;
3081 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3082 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3083 parse[parse_pos] = NULLCHAR;
3084 // try to be smart: if it does not look like search info, it should go to
3085 // ICS interaction window after all, not to engine-output window.
3086 for(j=0; j<parse_pos; j++) { // count letters and digits
3087 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3088 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3089 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3091 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3092 int depth=0; float score;
3093 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3094 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3095 pvInfoList[forwardMostMove-1].depth = depth;
3096 pvInfoList[forwardMostMove-1].score = 100*score;
3098 OutputKibitz(suppressKibitz, parse);
3101 if(gameMode == IcsObserving) // restore original ICS messages
3102 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3103 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3105 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3106 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3107 SendToPlayer(tmp, strlen(tmp));
3109 next_out = i+1; // [HGM] suppress printing in ICS window
3111 started = STARTED_NONE;
3113 /* Don't match patterns against characters in comment */
3118 if (started == STARTED_CHATTER) {
3119 if (buf[i] != '\n') {
3120 /* Don't match patterns against characters in chatter */
3124 started = STARTED_NONE;
3125 if(suppressKibitz) next_out = i+1;
3128 /* Kludge to deal with rcmd protocol */
3129 if (firstTime && looking_at(buf, &i, "\001*")) {
3130 DisplayFatalError(&buf[1], 0, 1);
3136 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3139 if (appData.debugMode)
3140 fprintf(debugFP, "ics_type %d\n", ics_type);
3143 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3144 ics_type = ICS_FICS;
3146 if (appData.debugMode)
3147 fprintf(debugFP, "ics_type %d\n", ics_type);
3150 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3151 ics_type = ICS_CHESSNET;
3153 if (appData.debugMode)
3154 fprintf(debugFP, "ics_type %d\n", ics_type);
3159 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3160 looking_at(buf, &i, "Logging you in as \"*\"") ||
3161 looking_at(buf, &i, "will be \"*\""))) {
3162 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3166 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3168 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3169 DisplayIcsInteractionTitle(buf);
3170 have_set_title = TRUE;
3173 /* skip finger notes */
3174 if (started == STARTED_NONE &&
3175 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3176 (buf[i] == '1' && buf[i+1] == '0')) &&
3177 buf[i+2] == ':' && buf[i+3] == ' ') {
3178 started = STARTED_CHATTER;
3184 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3185 if(appData.seekGraph) {
3186 if(soughtPending && MatchSoughtLine(buf+i)) {
3187 i = strstr(buf+i, "rated") - buf;
3188 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3189 next_out = leftover_start = i;
3190 started = STARTED_CHATTER;
3191 suppressKibitz = TRUE;
3194 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3195 && looking_at(buf, &i, "* ads displayed")) {
3196 soughtPending = FALSE;
3201 if(appData.autoRefresh) {
3202 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3203 int s = (ics_type == ICS_ICC); // ICC format differs
3205 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3206 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3207 looking_at(buf, &i, "*% "); // eat prompt
3208 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3209 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3210 next_out = i; // suppress
3213 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3214 char *p = star_match[0];
3216 if(seekGraphUp) RemoveSeekAd(atoi(p));
3217 while(*p && *p++ != ' '); // next
3219 looking_at(buf, &i, "*% "); // eat prompt
3220 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3227 /* skip formula vars */
3228 if (started == STARTED_NONE &&
3229 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3230 started = STARTED_CHATTER;
3235 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3236 if (appData.autoKibitz && started == STARTED_NONE &&
3237 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3238 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3239 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3240 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3241 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3242 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3243 suppressKibitz = TRUE;
3244 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3247 && (gameMode == IcsPlayingWhite)) ||
3248 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3249 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3250 started = STARTED_CHATTER; // own kibitz we simply discard
3252 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3253 parse_pos = 0; parse[0] = NULLCHAR;
3254 savingComment = TRUE;
3255 suppressKibitz = gameMode != IcsObserving ? 2 :
3256 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3260 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3261 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3262 && atoi(star_match[0])) {
3263 // suppress the acknowledgements of our own autoKibitz
3265 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3266 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3267 SendToPlayer(star_match[0], strlen(star_match[0]));
3268 if(looking_at(buf, &i, "*% ")) // eat prompt
3269 suppressKibitz = FALSE;
3273 } // [HGM] kibitz: end of patch
3275 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3277 // [HGM] chat: intercept tells by users for which we have an open chat window
3279 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3280 looking_at(buf, &i, "* whispers:") ||
3281 looking_at(buf, &i, "* kibitzes:") ||
3282 looking_at(buf, &i, "* shouts:") ||
3283 looking_at(buf, &i, "* c-shouts:") ||
3284 looking_at(buf, &i, "--> * ") ||
3285 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3286 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3287 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3288 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3290 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3291 chattingPartner = -1;
3293 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3294 for(p=0; p<MAX_CHAT; p++) {
3295 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3296 talker[0] = '['; strcat(talker, "] ");
3297 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3298 chattingPartner = p; break;
3301 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3302 for(p=0; p<MAX_CHAT; p++) {
3303 if(!strcmp("kibitzes", chatPartner[p])) {
3304 talker[0] = '['; strcat(talker, "] ");
3305 chattingPartner = p; break;
3308 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3309 for(p=0; p<MAX_CHAT; p++) {
3310 if(!strcmp("whispers", chatPartner[p])) {
3311 talker[0] = '['; strcat(talker, "] ");
3312 chattingPartner = p; break;
3315 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3316 if(buf[i-8] == '-' && buf[i-3] == 't')
3317 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3318 if(!strcmp("c-shouts", chatPartner[p])) {
3319 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3320 chattingPartner = p; break;
3323 if(chattingPartner < 0)
3324 for(p=0; p<MAX_CHAT; p++) {
3325 if(!strcmp("shouts", chatPartner[p])) {
3326 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3327 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3328 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3329 chattingPartner = p; break;
3333 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3334 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3335 talker[0] = 0; Colorize(ColorTell, FALSE);
3336 chattingPartner = p; break;
3338 if(chattingPartner<0) i = oldi; else {
3339 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3340 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3341 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3342 started = STARTED_COMMENT;
3343 parse_pos = 0; parse[0] = NULLCHAR;
3344 savingComment = 3 + chattingPartner; // counts as TRUE
3345 suppressKibitz = TRUE;
3348 } // [HGM] chat: end of patch
3351 if (appData.zippyTalk || appData.zippyPlay) {
3352 /* [DM] Backup address for color zippy lines */
3354 if (loggedOn == TRUE)
3355 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3356 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3358 } // [DM] 'else { ' deleted
3360 /* Regular tells and says */
3361 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3362 looking_at(buf, &i, "* (your partner) tells you: ") ||
3363 looking_at(buf, &i, "* says: ") ||
3364 /* Don't color "message" or "messages" output */
3365 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3366 looking_at(buf, &i, "*. * at *:*: ") ||
3367 looking_at(buf, &i, "--* (*:*): ") ||
3368 /* Message notifications (same color as tells) */
3369 looking_at(buf, &i, "* has left a message ") ||
3370 looking_at(buf, &i, "* just sent you a message:\n") ||
3371 /* Whispers and kibitzes */
3372 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3373 looking_at(buf, &i, "* kibitzes: ") ||
3375 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3377 if (tkind == 1 && strchr(star_match[0], ':')) {
3378 /* Avoid "tells you:" spoofs in channels */
3381 if (star_match[0][0] == NULLCHAR ||
3382 strchr(star_match[0], ' ') ||
3383 (tkind == 3 && strchr(star_match[1], ' '))) {
3384 /* Reject bogus matches */
3387 if (appData.colorize) {
3388 if (oldi > next_out) {
3389 SendToPlayer(&buf[next_out], oldi - next_out);
3394 Colorize(ColorTell, FALSE);
3395 curColor = ColorTell;
3398 Colorize(ColorKibitz, FALSE);
3399 curColor = ColorKibitz;
3402 p = strrchr(star_match[1], '(');
3409 Colorize(ColorChannel1, FALSE);
3410 curColor = ColorChannel1;
3412 Colorize(ColorChannel, FALSE);
3413 curColor = ColorChannel;
3417 curColor = ColorNormal;
3421 if (started == STARTED_NONE && appData.autoComment &&
3422 (gameMode == IcsObserving ||
3423 gameMode == IcsPlayingWhite ||
3424 gameMode == IcsPlayingBlack)) {
3425 parse_pos = i - oldi;
3426 memcpy(parse, &buf[oldi], parse_pos);
3427 parse[parse_pos] = NULLCHAR;
3428 started = STARTED_COMMENT;
3429 savingComment = TRUE;
3431 started = STARTED_CHATTER;
3432 savingComment = FALSE;
3439 if (looking_at(buf, &i, "* s-shouts: ") ||
3440 looking_at(buf, &i, "* c-shouts: ")) {
3441 if (appData.colorize) {
3442 if (oldi > next_out) {
3443 SendToPlayer(&buf[next_out], oldi - next_out);
3446 Colorize(ColorSShout, FALSE);
3447 curColor = ColorSShout;
3450 started = STARTED_CHATTER;
3454 if (looking_at(buf, &i, "--->")) {
3459 if (looking_at(buf, &i, "* shouts: ") ||
3460 looking_at(buf, &i, "--> ")) {
3461 if (appData.colorize) {
3462 if (oldi > next_out) {
3463 SendToPlayer(&buf[next_out], oldi - next_out);
3466 Colorize(ColorShout, FALSE);
3467 curColor = ColorShout;
3470 started = STARTED_CHATTER;
3474 if (looking_at( buf, &i, "Challenge:")) {
3475 if (appData.colorize) {
3476 if (oldi > next_out) {
3477 SendToPlayer(&buf[next_out], oldi - next_out);
3480 Colorize(ColorChallenge, FALSE);
3481 curColor = ColorChallenge;
3487 if (looking_at(buf, &i, "* offers you") ||
3488 looking_at(buf, &i, "* offers to be") ||
3489 looking_at(buf, &i, "* would like to") ||
3490 looking_at(buf, &i, "* requests to") ||
3491 looking_at(buf, &i, "Your opponent offers") ||
3492 looking_at(buf, &i, "Your opponent requests")) {
3494 if (appData.colorize) {
3495 if (oldi > next_out) {
3496 SendToPlayer(&buf[next_out], oldi - next_out);
3499 Colorize(ColorRequest, FALSE);
3500 curColor = ColorRequest;
3505 if (looking_at(buf, &i, "* (*) seeking")) {
3506 if (appData.colorize) {
3507 if (oldi > next_out) {
3508 SendToPlayer(&buf[next_out], oldi - next_out);
3511 Colorize(ColorSeek, FALSE);
3512 curColor = ColorSeek;
3517 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3519 if (looking_at(buf, &i, "\\ ")) {
3520 if (prevColor != ColorNormal) {
3521 if (oldi > next_out) {
3522 SendToPlayer(&buf[next_out], oldi - next_out);
3525 Colorize(prevColor, TRUE);
3526 curColor = prevColor;
3528 if (savingComment) {
3529 parse_pos = i - oldi;
3530 memcpy(parse, &buf[oldi], parse_pos);
3531 parse[parse_pos] = NULLCHAR;
3532 started = STARTED_COMMENT;
3533 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3534 chattingPartner = savingComment - 3; // kludge to remember the box
3536 started = STARTED_CHATTER;
3541 if (looking_at(buf, &i, "Black Strength :") ||
3542 looking_at(buf, &i, "<<< style 10 board >>>") ||
3543 looking_at(buf, &i, "<10>") ||
3544 looking_at(buf, &i, "#@#")) {
3545 /* Wrong board style */
3547 SendToICS(ics_prefix);
3548 SendToICS("set style 12\n");
3549 SendToICS(ics_prefix);
3550 SendToICS("refresh\n");
3554 if (looking_at(buf, &i, "login:")) {
3555 if (!have_sent_ICS_logon) {
3557 have_sent_ICS_logon = 1;
3558 else // no init script was found
3559 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3560 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3561 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3566 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3567 (looking_at(buf, &i, "\n<12> ") ||
3568 looking_at(buf, &i, "<12> "))) {
3570 if (oldi > next_out) {
3571 SendToPlayer(&buf[next_out], oldi - next_out);
3574 started = STARTED_BOARD;
3579 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3580 looking_at(buf, &i, "<b1> ")) {
3581 if (oldi > next_out) {
3582 SendToPlayer(&buf[next_out], oldi - next_out);
3585 started = STARTED_HOLDINGS;
3590 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3592 /* Header for a move list -- first line */
3594 switch (ics_getting_history) {
3598 case BeginningOfGame:
3599 /* User typed "moves" or "oldmoves" while we
3600 were idle. Pretend we asked for these
3601 moves and soak them up so user can step
3602 through them and/or save them.
3605 gameMode = IcsObserving;
3608 ics_getting_history = H_GOT_UNREQ_HEADER;
3610 case EditGame: /*?*/
3611 case EditPosition: /*?*/
3612 /* Should above feature work in these modes too? */
3613 /* For now it doesn't */
3614 ics_getting_history = H_GOT_UNWANTED_HEADER;
3617 ics_getting_history = H_GOT_UNWANTED_HEADER;
3622 /* Is this the right one? */
3623 if (gameInfo.white && gameInfo.black &&
3624 strcmp(gameInfo.white, star_match[0]) == 0 &&
3625 strcmp(gameInfo.black, star_match[2]) == 0) {
3627 ics_getting_history = H_GOT_REQ_HEADER;
3630 case H_GOT_REQ_HEADER:
3631 case H_GOT_UNREQ_HEADER:
3632 case H_GOT_UNWANTED_HEADER:
3633 case H_GETTING_MOVES:
3634 /* Should not happen */
3635 DisplayError(_("Error gathering move list: two headers"), 0);
3636 ics_getting_history = H_FALSE;
3640 /* Save player ratings into gameInfo if needed */
3641 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3642 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3643 (gameInfo.whiteRating == -1 ||
3644 gameInfo.blackRating == -1)) {
3646 gameInfo.whiteRating = string_to_rating(star_match[1]);
3647 gameInfo.blackRating = string_to_rating(star_match[3]);
3648 if (appData.debugMode)
3649 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3650 gameInfo.whiteRating, gameInfo.blackRating);
3655 if (looking_at(buf, &i,
3656 "* * match, initial time: * minute*, increment: * second")) {
3657 /* Header for a move list -- second line */
3658 /* Initial board will follow if this is a wild game */
3659 if (gameInfo.event != NULL) free(gameInfo.event);
3660 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3661 gameInfo.event = StrSave(str);
3662 /* [HGM] we switched variant. Translate boards if needed. */
3663 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3667 if (looking_at(buf, &i, "Move ")) {
3668 /* Beginning of a move list */
3669 switch (ics_getting_history) {
3671 /* Normally should not happen */
3672 /* Maybe user hit reset while we were parsing */
3675 /* Happens if we are ignoring a move list that is not
3676 * the one we just requested. Common if the user
3677 * tries to observe two games without turning off
3680 case H_GETTING_MOVES:
3681 /* Should not happen */
3682 DisplayError(_("Error gathering move list: nested"), 0);
3683 ics_getting_history = H_FALSE;
3685 case H_GOT_REQ_HEADER:
3686 ics_getting_history = H_GETTING_MOVES;
3687 started = STARTED_MOVES;
3689 if (oldi > next_out) {
3690 SendToPlayer(&buf[next_out], oldi - next_out);
3693 case H_GOT_UNREQ_HEADER:
3694 ics_getting_history = H_GETTING_MOVES;
3695 started = STARTED_MOVES_NOHIDE;
3698 case H_GOT_UNWANTED_HEADER:
3699 ics_getting_history = H_FALSE;
3705 if (looking_at(buf, &i, "% ") ||
3706 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3707 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3708 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3709 soughtPending = FALSE;
3713 if(suppressKibitz) next_out = i;
3714 savingComment = FALSE;
3718 case STARTED_MOVES_NOHIDE:
3719 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3720 parse[parse_pos + i - oldi] = NULLCHAR;
3721 ParseGameHistory(parse);
3723 if (appData.zippyPlay && first.initDone) {
3724 FeedMovesToProgram(&first, forwardMostMove);
3725 if (gameMode == IcsPlayingWhite) {
3726 if (WhiteOnMove(forwardMostMove)) {
3727 if (first.sendTime) {
3728 if (first.useColors) {
3729 SendToProgram("black\n", &first);
3731 SendTimeRemaining(&first, TRUE);
3733 if (first.useColors) {
3734 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3736 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3737 first.maybeThinking = TRUE;
3739 if (first.usePlayother) {
3740 if (first.sendTime) {
3741 SendTimeRemaining(&first, TRUE);
3743 SendToProgram("playother\n", &first);
3749 } else if (gameMode == IcsPlayingBlack) {
3750 if (!WhiteOnMove(forwardMostMove)) {
3751 if (first.sendTime) {
3752 if (first.useColors) {
3753 SendToProgram("white\n", &first);
3755 SendTimeRemaining(&first, FALSE);
3757 if (first.useColors) {
3758 SendToProgram("black\n", &first);
3760 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3761 first.maybeThinking = TRUE;
3763 if (first.usePlayother) {
3764 if (first.sendTime) {
3765 SendTimeRemaining(&first, FALSE);
3767 SendToProgram("playother\n", &first);
3776 if (gameMode == IcsObserving && ics_gamenum == -1) {
3777 /* Moves came from oldmoves or moves command
3778 while we weren't doing anything else.
3780 currentMove = forwardMostMove;
3781 ClearHighlights();/*!!could figure this out*/
3782 flipView = appData.flipView;
3783 DrawPosition(TRUE, boards[currentMove]);
3784 DisplayBothClocks();
3785 snprintf(str, MSG_SIZ, "%s %s %s",
3786 gameInfo.white, _("vs."), gameInfo.black);
3790 /* Moves were history of an active game */
3791 if (gameInfo.resultDetails != NULL) {
3792 free(gameInfo.resultDetails);
3793 gameInfo.resultDetails = NULL;
3796 HistorySet(parseList, backwardMostMove,
3797 forwardMostMove, currentMove-1);
3798 DisplayMove(currentMove - 1);
3799 if (started == STARTED_MOVES) next_out = i;
3800 started = STARTED_NONE;
3801 ics_getting_history = H_FALSE;
3804 case STARTED_OBSERVE:
3805 started = STARTED_NONE;
3806 SendToICS(ics_prefix);
3807 SendToICS("refresh\n");
3813 if(bookHit) { // [HGM] book: simulate book reply
3814 static char bookMove[MSG_SIZ]; // a bit generous?
3816 programStats.nodes = programStats.depth = programStats.time =
3817 programStats.score = programStats.got_only_move = 0;
3818 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3820 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3821 strcat(bookMove, bookHit);
3822 HandleMachineMove(bookMove, &first);
3827 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3828 started == STARTED_HOLDINGS ||
3829 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3830 /* Accumulate characters in move list or board */
3831 parse[parse_pos++] = buf[i];
3834 /* Start of game messages. Mostly we detect start of game
3835 when the first board image arrives. On some versions
3836 of the ICS, though, we need to do a "refresh" after starting
3837 to observe in order to get the current board right away. */
3838 if (looking_at(buf, &i, "Adding game * to observation list")) {
3839 started = STARTED_OBSERVE;
3843 /* Handle auto-observe */
3844 if (appData.autoObserve &&
3845 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3846 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3848 /* Choose the player that was highlighted, if any. */
3849 if (star_match[0][0] == '\033' ||
3850 star_match[1][0] != '\033') {
3851 player = star_match[0];
3853 player = star_match[2];
3855 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3856 ics_prefix, StripHighlightAndTitle(player));
3859 /* Save ratings from notify string */
3860 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3861 player1Rating = string_to_rating(star_match[1]);
3862 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3863 player2Rating = string_to_rating(star_match[3]);
3865 if (appData.debugMode)
3867 "Ratings from 'Game notification:' %s %d, %s %d\n",
3868 player1Name, player1Rating,
3869 player2Name, player2Rating);
3874 /* Deal with automatic examine mode after a game,
3875 and with IcsObserving -> IcsExamining transition */
3876 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3877 looking_at(buf, &i, "has made you an examiner of&nb