2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
63 # define EGBB_NAME "egbbdll64.dll"
65 # define EGBB_NAME "egbbdll.dll"
70 # include <sys/file.h>
75 # define EGBB_NAME "egbbso64.so"
77 # define EGBB_NAME "egbbso.so"
79 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
81 # define HMODULE void *
82 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 # define GetProcAddress dlsym
93 #include <sys/types.h>
102 #else /* not STDC_HEADERS */
105 # else /* not HAVE_STRING_H */
106 # include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
123 # include <sys/time.h>
129 #if defined(_amigados) && !defined(__GNUC__)
134 extern int gettimeofday(struct timeval *, struct timezone *);
142 #include "frontend.h"
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172 char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174 char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187 /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
272 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
299 /* States for ics_getting_history */
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
307 /* whosays values for GameEnds */
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
319 /* Different types of move when calling RegisterMove */
321 #define CMAIL_RESIGN 1
323 #define CMAIL_ACCEPT 3
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
330 /* Telnet protocol constants */
341 safeStrCpy (char *dst, const char *src, size_t count)
344 assert( dst != NULL );
345 assert( src != NULL );
348 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349 if( i == count && dst[count-1] != NULLCHAR)
351 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352 if(appData.debugMode)
353 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
359 /* Some compiler can't cast u64 to double
360 * This function do the job for us:
362 * We use the highest bit for cast, this only
363 * works if the highest bit is not
364 * in use (This should not happen)
366 * We used this for all compiler
369 u64ToDouble (u64 value)
372 u64 tmp = value & u64Const(0x7fffffffffffffff);
373 r = (double)(s64)tmp;
374 if (value & u64Const(0x8000000000000000))
375 r += 9.2233720368547758080e18; /* 2^63 */
379 /* Fake up flags for now, as we aren't keeping track of castling
380 availability yet. [HGM] Change of logic: the flag now only
381 indicates the type of castlings allowed by the rule of the game.
382 The actual rights themselves are maintained in the array
383 castlingRights, as part of the game history, and are not probed
389 int flags = F_ALL_CASTLE_OK;
390 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391 switch (gameInfo.variant) {
393 flags &= ~F_ALL_CASTLE_OK;
394 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395 flags |= F_IGNORE_CHECK;
397 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402 case VariantKriegspiel:
403 flags |= F_KRIEGSPIEL_CAPTURE;
405 case VariantCapaRandom:
406 case VariantFischeRandom:
407 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408 case VariantNoCastle:
409 case VariantShatranj:
414 flags &= ~F_ALL_CASTLE_OK;
419 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
423 FILE *gameFileFP, *debugFP, *serverFP;
424 char *currentDebugFile; // [HGM] debug split: to remember name
427 [AS] Note: sometimes, the sscanf() function is used to parse the input
428 into a fixed-size buffer. Because of this, we must be prepared to
429 receive strings as long as the size of the input buffer, which is currently
430 set to 4K for Windows and 8K for the rest.
431 So, we must either allocate sufficiently large buffers here, or
432 reduce the size of the input buffer in the input reading part.
435 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
436 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
437 char thinkOutput1[MSG_SIZ*10];
439 ChessProgramState first, second, pairing;
441 /* premove variables */
444 int premoveFromX = 0;
445 int premoveFromY = 0;
446 int premovePromoChar = 0;
448 Boolean alarmSounded;
449 /* end premove variables */
451 char *ics_prefix = "$";
452 enum ICS_TYPE ics_type = ICS_GENERIC;
454 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
455 int pauseExamForwardMostMove = 0;
456 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
457 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
458 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
459 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
460 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
461 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
462 int whiteFlag = FALSE, blackFlag = FALSE;
463 int userOfferedDraw = FALSE;
464 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
465 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
466 int cmailMoveType[CMAIL_MAX_GAMES];
467 long ics_clock_paused = 0;
468 ProcRef icsPR = NoProc, cmailPR = NoProc;
469 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
470 GameMode gameMode = BeginningOfGame;
471 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
472 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
473 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
474 int hiddenThinkOutputState = 0; /* [AS] */
475 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
476 int adjudicateLossPlies = 6;
477 char white_holding[64], black_holding[64];
478 TimeMark lastNodeCountTime;
479 long lastNodeCount=0;
480 int shiftKey, controlKey; // [HGM] set by mouse handler
482 int have_sent_ICS_logon = 0;
484 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
485 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
486 Boolean adjustedClock;
487 long timeControl_2; /* [AS] Allow separate time controls */
488 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
489 long timeRemaining[2][MAX_MOVES];
490 int matchGame = 0, nextGame = 0, roundNr = 0;
491 Boolean waitingForGame = FALSE, startingEngine = FALSE;
492 TimeMark programStartTime, pauseStart;
493 char ics_handle[MSG_SIZ];
494 int have_set_title = 0;
496 /* animateTraining preserves the state of appData.animate
497 * when Training mode is activated. This allows the
498 * response to be animated when appData.animate == TRUE and
499 * appData.animateDragging == TRUE.
501 Boolean animateTraining;
507 Board boards[MAX_MOVES];
508 /* [HGM] Following 7 needed for accurate legality tests: */
509 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
510 signed char initialRights[BOARD_FILES];
511 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
512 int initialRulePlies, FENrulePlies;
513 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
515 Boolean shuffleOpenings;
516 int mute; // mute all sounds
518 // [HGM] vari: next 12 to save and restore variations
519 #define MAX_VARIATIONS 10
520 int framePtr = MAX_MOVES-1; // points to free stack entry
522 int savedFirst[MAX_VARIATIONS];
523 int savedLast[MAX_VARIATIONS];
524 int savedFramePtr[MAX_VARIATIONS];
525 char *savedDetails[MAX_VARIATIONS];
526 ChessMove savedResult[MAX_VARIATIONS];
528 void PushTail P((int firstMove, int lastMove));
529 Boolean PopTail P((Boolean annotate));
530 void PushInner P((int firstMove, int lastMove));
531 void PopInner P((Boolean annotate));
532 void CleanupTail P((void));
534 ChessSquare FIDEArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
538 BlackKing, BlackBishop, BlackKnight, BlackRook }
541 ChessSquare twoKingsArray[2][BOARD_FILES] = {
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
545 BlackKing, BlackKing, BlackKnight, BlackRook }
548 ChessSquare KnightmateArray[2][BOARD_FILES] = {
549 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
550 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
551 { BlackRook, BlackMan, BlackBishop, BlackQueen,
552 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
555 ChessSquare SpartanArray[2][BOARD_FILES] = {
556 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
557 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
558 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
559 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
562 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
563 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
564 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
565 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
566 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
569 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
571 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
573 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
577 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
578 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackMan, BlackFerz,
580 BlackKing, BlackMan, BlackKnight, BlackRook }
583 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
584 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
585 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackMan, BlackFerz,
587 BlackKing, BlackMan, BlackKnight, BlackRook }
590 ChessSquare lionArray[2][BOARD_FILES] = {
591 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
592 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
593 { BlackRook, BlackLion, BlackBishop, BlackQueen,
594 BlackKing, BlackBishop, BlackKnight, BlackRook }
598 #if (BOARD_FILES>=10)
599 ChessSquare ShogiArray[2][BOARD_FILES] = {
600 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
601 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
602 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
603 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
606 ChessSquare XiangqiArray[2][BOARD_FILES] = {
607 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
608 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
609 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
610 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
613 ChessSquare CapablancaArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
615 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
617 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
620 ChessSquare GreatArray[2][BOARD_FILES] = {
621 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
622 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
623 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
624 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
627 ChessSquare JanusArray[2][BOARD_FILES] = {
628 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
629 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
630 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
631 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
634 ChessSquare GrandArray[2][BOARD_FILES] = {
635 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
636 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
637 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
638 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
641 ChessSquare ChuChessArray[2][BOARD_FILES] = {
642 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
643 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
644 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
645 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
649 ChessSquare GothicArray[2][BOARD_FILES] = {
650 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
651 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
652 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
653 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
656 #define GothicArray CapablancaArray
660 ChessSquare FalconArray[2][BOARD_FILES] = {
661 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
662 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
663 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
664 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
667 #define FalconArray CapablancaArray
670 #else // !(BOARD_FILES>=10)
671 #define XiangqiPosition FIDEArray
672 #define CapablancaArray FIDEArray
673 #define GothicArray FIDEArray
674 #define GreatArray FIDEArray
675 #endif // !(BOARD_FILES>=10)
677 #if (BOARD_FILES>=12)
678 ChessSquare CourierArray[2][BOARD_FILES] = {
679 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
680 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
681 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
682 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
684 ChessSquare ChuArray[6][BOARD_FILES] = {
685 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
686 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
687 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
688 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
689 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
690 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
691 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
692 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
693 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
694 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
695 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
696 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
698 #else // !(BOARD_FILES>=12)
699 #define CourierArray CapablancaArray
700 #define ChuArray CapablancaArray
701 #endif // !(BOARD_FILES>=12)
704 Board initialPosition;
707 /* Convert str to a rating. Checks for special cases of "----",
709 "++++", etc. Also strips ()'s */
711 string_to_rating (char *str)
713 while(*str && !isdigit(*str)) ++str;
715 return 0; /* One of the special "no rating" cases */
723 /* Init programStats */
724 programStats.movelist[0] = 0;
725 programStats.depth = 0;
726 programStats.nr_moves = 0;
727 programStats.moves_left = 0;
728 programStats.nodes = 0;
729 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
730 programStats.score = 0;
731 programStats.got_only_move = 0;
732 programStats.got_fail = 0;
733 programStats.line_is_book = 0;
738 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
739 if (appData.firstPlaysBlack) {
740 first.twoMachinesColor = "black\n";
741 second.twoMachinesColor = "white\n";
743 first.twoMachinesColor = "white\n";
744 second.twoMachinesColor = "black\n";
747 first.other = &second;
748 second.other = &first;
751 if(appData.timeOddsMode) {
752 norm = appData.timeOdds[0];
753 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
755 first.timeOdds = appData.timeOdds[0]/norm;
756 second.timeOdds = appData.timeOdds[1]/norm;
759 if(programVersion) free(programVersion);
760 if (appData.noChessProgram) {
761 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
762 sprintf(programVersion, "%s", PACKAGE_STRING);
764 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
765 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
766 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
771 UnloadEngine (ChessProgramState *cps)
773 /* Kill off first chess program */
774 if (cps->isr != NULL)
775 RemoveInputSource(cps->isr);
778 if (cps->pr != NoProc) {
780 DoSleep( appData.delayBeforeQuit );
781 SendToProgram("quit\n", cps);
782 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
785 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
789 ClearOptions (ChessProgramState *cps)
792 cps->nrOptions = cps->comboCnt = 0;
793 for(i=0; i<MAX_OPTIONS; i++) {
794 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
795 cps->option[i].textValue = 0;
799 char *engineNames[] = {
800 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
801 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
803 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
804 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
809 InitEngine (ChessProgramState *cps, int n)
810 { // [HGM] all engine initialiation put in a function that does one engine
814 cps->which = engineNames[n];
815 cps->maybeThinking = FALSE;
819 cps->sendDrawOffers = 1;
821 cps->program = appData.chessProgram[n];
822 cps->host = appData.host[n];
823 cps->dir = appData.directory[n];
824 cps->initString = appData.engInitString[n];
825 cps->computerString = appData.computerString[n];
826 cps->useSigint = TRUE;
827 cps->useSigterm = TRUE;
828 cps->reuse = appData.reuse[n];
829 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
830 cps->useSetboard = FALSE;
832 cps->usePing = FALSE;
835 cps->usePlayother = FALSE;
836 cps->useColors = TRUE;
837 cps->useUsermove = FALSE;
838 cps->sendICS = FALSE;
839 cps->sendName = appData.icsActive;
840 cps->sdKludge = FALSE;
841 cps->stKludge = FALSE;
842 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
843 TidyProgramName(cps->program, cps->host, cps->tidy);
845 ASSIGN(cps->variants, appData.variant);
846 cps->analysisSupport = 2; /* detect */
847 cps->analyzing = FALSE;
848 cps->initDone = FALSE;
850 cps->pseudo = appData.pseudo[n];
852 /* New features added by Tord: */
853 cps->useFEN960 = FALSE;
854 cps->useOOCastle = TRUE;
855 /* End of new features added by Tord. */
856 cps->fenOverride = appData.fenOverride[n];
858 /* [HGM] time odds: set factor for each machine */
859 cps->timeOdds = appData.timeOdds[n];
861 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
862 cps->accumulateTC = appData.accumulateTC[n];
863 cps->maxNrOfSessions = 1;
868 cps->drawDepth = appData.drawDepth[n];
869 cps->supportsNPS = UNKNOWN;
870 cps->memSize = FALSE;
871 cps->maxCores = FALSE;
872 ASSIGN(cps->egtFormats, "");
875 cps->optionSettings = appData.engOptions[n];
877 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
878 cps->isUCI = appData.isUCI[n]; /* [AS] */
879 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
882 if (appData.protocolVersion[n] > PROTOVER
883 || appData.protocolVersion[n] < 1)
888 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
889 appData.protocolVersion[n]);
890 if( (len >= MSG_SIZ) && appData.debugMode )
891 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
893 DisplayFatalError(buf, 0, 2);
897 cps->protocolVersion = appData.protocolVersion[n];
900 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
901 ParseFeatures(appData.featureDefaults, cps);
904 ChessProgramState *savCps;
912 if(WaitForEngine(savCps, LoadEngine)) return;
913 CommonEngineInit(); // recalculate time odds
914 if(gameInfo.variant != StringToVariant(appData.variant)) {
915 // we changed variant when loading the engine; this forces us to reset
916 Reset(TRUE, savCps != &first);
917 oldMode = BeginningOfGame; // to prevent restoring old mode
919 InitChessProgram(savCps, FALSE);
920 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
921 DisplayMessage("", "");
922 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
923 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
926 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
930 ReplaceEngine (ChessProgramState *cps, int n)
932 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
934 if(oldMode != BeginningOfGame) EditGameEvent();
937 appData.noChessProgram = FALSE;
938 appData.clockMode = TRUE;
941 if(n) return; // only startup first engine immediately; second can wait
942 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
946 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
947 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
949 static char resetOptions[] =
950 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
951 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
952 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
953 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
956 FloatToFront(char **list, char *engineLine)
958 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
960 if(appData.recentEngines <= 0) return;
961 TidyProgramName(engineLine, "localhost", tidy+1);
962 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
963 strncpy(buf+1, *list, MSG_SIZ-50);
964 if(p = strstr(buf, tidy)) { // tidy name appears in list
965 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
966 while(*p++ = *++q); // squeeze out
968 strcat(tidy, buf+1); // put list behind tidy name
969 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
970 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
971 ASSIGN(*list, tidy+1);
974 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
977 Load (ChessProgramState *cps, int i)
979 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
980 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
981 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
982 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
983 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
984 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
985 appData.firstProtocolVersion = PROTOVER;
986 ParseArgsFromString(buf);
988 ReplaceEngine(cps, i);
989 FloatToFront(&appData.recentEngineList, engineLine);
993 while(q = strchr(p, SLASH)) p = q+1;
994 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
995 if(engineDir[0] != NULLCHAR) {
996 ASSIGN(appData.directory[i], engineDir); p = engineName;
997 } else if(p != engineName) { // derive directory from engine path, when not given
999 ASSIGN(appData.directory[i], engineName);
1001 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1002 } else { ASSIGN(appData.directory[i], "."); }
1003 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1005 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1006 snprintf(command, MSG_SIZ, "%s %s", p, params);
1009 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1010 ASSIGN(appData.chessProgram[i], p);
1011 appData.isUCI[i] = isUCI;
1012 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1013 appData.hasOwnBookUCI[i] = hasBook;
1014 if(!nickName[0]) useNick = FALSE;
1015 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1019 q = firstChessProgramNames;
1020 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1021 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1022 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1023 quote, p, quote, appData.directory[i],
1024 useNick ? " -fn \"" : "",
1025 useNick ? nickName : "",
1026 useNick ? "\"" : "",
1027 v1 ? " -firstProtocolVersion 1" : "",
1028 hasBook ? "" : " -fNoOwnBookUCI",
1029 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1030 storeVariant ? " -variant " : "",
1031 storeVariant ? VariantName(gameInfo.variant) : "");
1032 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1033 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1034 if(insert != q) insert[-1] = NULLCHAR;
1035 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1037 FloatToFront(&appData.recentEngineList, buf);
1039 ReplaceEngine(cps, i);
1045 int matched, min, sec;
1047 * Parse timeControl resource
1049 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1050 appData.movesPerSession)) {
1052 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1053 DisplayFatalError(buf, 0, 2);
1057 * Parse searchTime resource
1059 if (*appData.searchTime != NULLCHAR) {
1060 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1062 searchTime = min * 60;
1063 } else if (matched == 2) {
1064 searchTime = min * 60 + sec;
1067 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1068 DisplayFatalError(buf, 0, 2);
1077 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1078 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1080 GetTimeMark(&programStartTime);
1081 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1082 appData.seedBase = random() + (random()<<15);
1083 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1085 ClearProgramStats();
1086 programStats.ok_to_send = 1;
1087 programStats.seen_stat = 0;
1090 * Initialize game list
1096 * Internet chess server status
1098 if (appData.icsActive) {
1099 appData.matchMode = FALSE;
1100 appData.matchGames = 0;
1102 appData.noChessProgram = !appData.zippyPlay;
1104 appData.zippyPlay = FALSE;
1105 appData.zippyTalk = FALSE;
1106 appData.noChessProgram = TRUE;
1108 if (*appData.icsHelper != NULLCHAR) {
1109 appData.useTelnet = TRUE;
1110 appData.telnetProgram = appData.icsHelper;
1113 appData.zippyTalk = appData.zippyPlay = FALSE;
1116 /* [AS] Initialize pv info list [HGM] and game state */
1120 for( i=0; i<=framePtr; i++ ) {
1121 pvInfoList[i].depth = -1;
1122 boards[i][EP_STATUS] = EP_NONE;
1123 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1129 /* [AS] Adjudication threshold */
1130 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1132 InitEngine(&first, 0);
1133 InitEngine(&second, 1);
1136 pairing.which = "pairing"; // pairing engine
1137 pairing.pr = NoProc;
1139 pairing.program = appData.pairingEngine;
1140 pairing.host = "localhost";
1143 if (appData.icsActive) {
1144 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1145 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1146 appData.clockMode = FALSE;
1147 first.sendTime = second.sendTime = 0;
1151 /* Override some settings from environment variables, for backward
1152 compatibility. Unfortunately it's not feasible to have the env
1153 vars just set defaults, at least in xboard. Ugh.
1155 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1160 if (!appData.icsActive) {
1164 /* Check for variants that are supported only in ICS mode,
1165 or not at all. Some that are accepted here nevertheless
1166 have bugs; see comments below.
1168 VariantClass variant = StringToVariant(appData.variant);
1170 case VariantBughouse: /* need four players and two boards */
1171 case VariantKriegspiel: /* need to hide pieces and move details */
1172 /* case VariantFischeRandom: (Fabien: moved below) */
1173 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1174 if( (len >= MSG_SIZ) && appData.debugMode )
1175 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1177 DisplayFatalError(buf, 0, 2);
1180 case VariantUnknown:
1181 case VariantLoadable:
1191 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1192 if( (len >= MSG_SIZ) && appData.debugMode )
1193 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1195 DisplayFatalError(buf, 0, 2);
1198 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1199 case VariantFairy: /* [HGM] TestLegality definitely off! */
1200 case VariantGothic: /* [HGM] should work */
1201 case VariantCapablanca: /* [HGM] should work */
1202 case VariantCourier: /* [HGM] initial forced moves not implemented */
1203 case VariantShogi: /* [HGM] could still mate with pawn drop */
1204 case VariantChu: /* [HGM] experimental */
1205 case VariantKnightmate: /* [HGM] should work */
1206 case VariantCylinder: /* [HGM] untested */
1207 case VariantFalcon: /* [HGM] untested */
1208 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1209 offboard interposition not understood */
1210 case VariantNormal: /* definitely works! */
1211 case VariantWildCastle: /* pieces not automatically shuffled */
1212 case VariantNoCastle: /* pieces not automatically shuffled */
1213 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1214 case VariantLosers: /* should work except for win condition,
1215 and doesn't know captures are mandatory */
1216 case VariantSuicide: /* should work except for win condition,
1217 and doesn't know captures are mandatory */
1218 case VariantGiveaway: /* should work except for win condition,
1219 and doesn't know captures are mandatory */
1220 case VariantTwoKings: /* should work */
1221 case VariantAtomic: /* should work except for win condition */
1222 case Variant3Check: /* should work except for win condition */
1223 case VariantShatranj: /* should work except for all win conditions */
1224 case VariantMakruk: /* should work except for draw countdown */
1225 case VariantASEAN : /* should work except for draw countdown */
1226 case VariantBerolina: /* might work if TestLegality is off */
1227 case VariantCapaRandom: /* should work */
1228 case VariantJanus: /* should work */
1229 case VariantSuper: /* experimental */
1230 case VariantGreat: /* experimental, requires legality testing to be off */
1231 case VariantSChess: /* S-Chess, should work */
1232 case VariantGrand: /* should work */
1233 case VariantSpartan: /* should work */
1234 case VariantLion: /* should work */
1235 case VariantChuChess: /* should work */
1243 NextIntegerFromString (char ** str, long * value)
1248 while( *s == ' ' || *s == '\t' ) {
1254 if( *s >= '0' && *s <= '9' ) {
1255 while( *s >= '0' && *s <= '9' ) {
1256 *value = *value * 10 + (*s - '0');
1269 NextTimeControlFromString (char ** str, long * value)
1272 int result = NextIntegerFromString( str, &temp );
1275 *value = temp * 60; /* Minutes */
1276 if( **str == ':' ) {
1278 result = NextIntegerFromString( str, &temp );
1279 *value += temp; /* Seconds */
1287 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1288 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1289 int result = -1, type = 0; long temp, temp2;
1291 if(**str != ':') return -1; // old params remain in force!
1293 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1294 if( NextIntegerFromString( str, &temp ) ) return -1;
1295 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1298 /* time only: incremental or sudden-death time control */
1299 if(**str == '+') { /* increment follows; read it */
1301 if(**str == '!') type = *(*str)++; // Bronstein TC
1302 if(result = NextIntegerFromString( str, &temp2)) return -1;
1303 *inc = temp2 * 1000;
1304 if(**str == '.') { // read fraction of increment
1305 char *start = ++(*str);
1306 if(result = NextIntegerFromString( str, &temp2)) return -1;
1308 while(start++ < *str) temp2 /= 10;
1312 *moves = 0; *tc = temp * 1000; *incType = type;
1316 (*str)++; /* classical time control */
1317 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1329 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1330 { /* [HGM] get time to add from the multi-session time-control string */
1331 int incType, moves=1; /* kludge to force reading of first session */
1332 long time, increment;
1335 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1337 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1338 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1339 if(movenr == -1) return time; /* last move before new session */
1340 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1341 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1342 if(!moves) return increment; /* current session is incremental */
1343 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1344 } while(movenr >= -1); /* try again for next session */
1346 return 0; // no new time quota on this move
1350 ParseTimeControl (char *tc, float ti, int mps)
1354 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1357 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1358 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1359 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1363 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1365 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1368 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1370 snprintf(buf, MSG_SIZ, ":%s", mytc);
1372 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1374 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1379 /* Parse second time control */
1382 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1390 timeControl_2 = tc2 * 1000;
1400 timeControl = tc1 * 1000;
1403 timeIncrement = ti * 1000; /* convert to ms */
1404 movesPerSession = 0;
1407 movesPerSession = mps;
1415 if (appData.debugMode) {
1416 # ifdef __GIT_VERSION
1417 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1419 fprintf(debugFP, "Version: %s\n", programVersion);
1422 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1424 set_cont_sequence(appData.wrapContSeq);
1425 if (appData.matchGames > 0) {
1426 appData.matchMode = TRUE;
1427 } else if (appData.matchMode) {
1428 appData.matchGames = 1;
1430 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1431 appData.matchGames = appData.sameColorGames;
1432 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1433 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1434 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1437 if (appData.noChessProgram || first.protocolVersion == 1) {
1440 /* kludge: allow timeout for initial "feature" commands */
1442 DisplayMessage("", _("Starting chess program"));
1443 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1448 CalculateIndex (int index, int gameNr)
1449 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1451 if(index > 0) return index; // fixed nmber
1452 if(index == 0) return 1;
1453 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1454 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1459 LoadGameOrPosition (int gameNr)
1460 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1461 if (*appData.loadGameFile != NULLCHAR) {
1462 if (!LoadGameFromFile(appData.loadGameFile,
1463 CalculateIndex(appData.loadGameIndex, gameNr),
1464 appData.loadGameFile, FALSE)) {
1465 DisplayFatalError(_("Bad game file"), 0, 1);
1468 } else if (*appData.loadPositionFile != NULLCHAR) {
1469 if (!LoadPositionFromFile(appData.loadPositionFile,
1470 CalculateIndex(appData.loadPositionIndex, gameNr),
1471 appData.loadPositionFile)) {
1472 DisplayFatalError(_("Bad position file"), 0, 1);
1480 ReserveGame (int gameNr, char resChar)
1482 FILE *tf = fopen(appData.tourneyFile, "r+");
1483 char *p, *q, c, buf[MSG_SIZ];
1484 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1485 safeStrCpy(buf, lastMsg, MSG_SIZ);
1486 DisplayMessage(_("Pick new game"), "");
1487 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1488 ParseArgsFromFile(tf);
1489 p = q = appData.results;
1490 if(appData.debugMode) {
1491 char *r = appData.participants;
1492 fprintf(debugFP, "results = '%s'\n", p);
1493 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1494 fprintf(debugFP, "\n");
1496 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1498 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1499 safeStrCpy(q, p, strlen(p) + 2);
1500 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1501 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1502 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1503 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1506 fseek(tf, -(strlen(p)+4), SEEK_END);
1508 if(c != '"') // depending on DOS or Unix line endings we can be one off
1509 fseek(tf, -(strlen(p)+2), SEEK_END);
1510 else fseek(tf, -(strlen(p)+3), SEEK_END);
1511 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1512 DisplayMessage(buf, "");
1513 free(p); appData.results = q;
1514 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1515 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1516 int round = appData.defaultMatchGames * appData.tourneyType;
1517 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1518 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1519 UnloadEngine(&first); // next game belongs to other pairing;
1520 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1522 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1526 MatchEvent (int mode)
1527 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1529 if(matchMode) { // already in match mode: switch it off
1531 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1534 // if(gameMode != BeginningOfGame) {
1535 // DisplayError(_("You can only start a match from the initial position."), 0);
1539 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1540 /* Set up machine vs. machine match */
1542 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1543 if(appData.tourneyFile[0]) {
1545 if(nextGame > appData.matchGames) {
1547 if(strchr(appData.results, '*') == NULL) {
1549 appData.tourneyCycles++;
1550 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1552 NextTourneyGame(-1, &dummy);
1554 if(nextGame <= appData.matchGames) {
1555 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1557 ScheduleDelayedEvent(NextMatchGame, 10000);
1562 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1563 DisplayError(buf, 0);
1564 appData.tourneyFile[0] = 0;
1568 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1569 DisplayFatalError(_("Can't have a match with no chess programs"),
1574 matchGame = roundNr = 1;
1575 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1579 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1582 InitBackEnd3 P((void))
1584 GameMode initialMode;
1588 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1589 !strcmp(appData.variant, "normal") && // no explicit variant request
1590 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1591 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1592 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1593 char c, *q = first.variants, *p = strchr(q, ',');
1594 if(p) *p = NULLCHAR;
1595 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1597 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1598 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1599 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1600 Reset(TRUE, FALSE); // and re-initialize
1605 InitChessProgram(&first, startedFromSetupPosition);
1607 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1608 free(programVersion);
1609 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1610 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1611 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1614 if (appData.icsActive) {
1616 /* [DM] Make a console window if needed [HGM] merged ifs */
1622 if (*appData.icsCommPort != NULLCHAR)
1623 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1624 appData.icsCommPort);
1626 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1627 appData.icsHost, appData.icsPort);
1629 if( (len >= MSG_SIZ) && appData.debugMode )
1630 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1632 DisplayFatalError(buf, err, 1);
1637 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1639 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1640 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1641 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1642 } else if (appData.noChessProgram) {
1648 if (*appData.cmailGameName != NULLCHAR) {
1650 OpenLoopback(&cmailPR);
1652 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1656 DisplayMessage("", "");
1657 if (StrCaseCmp(appData.initialMode, "") == 0) {
1658 initialMode = BeginningOfGame;
1659 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1660 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1661 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1662 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1665 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1666 initialMode = TwoMachinesPlay;
1667 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1668 initialMode = AnalyzeFile;
1669 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1670 initialMode = AnalyzeMode;
1671 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1672 initialMode = MachinePlaysWhite;
1673 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1674 initialMode = MachinePlaysBlack;
1675 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1676 initialMode = EditGame;
1677 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1678 initialMode = EditPosition;
1679 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1680 initialMode = Training;
1682 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1683 if( (len >= MSG_SIZ) && appData.debugMode )
1684 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1686 DisplayFatalError(buf, 0, 2);
1690 if (appData.matchMode) {
1691 if(appData.tourneyFile[0]) { // start tourney from command line
1693 if(f = fopen(appData.tourneyFile, "r")) {
1694 ParseArgsFromFile(f); // make sure tourney parmeters re known
1696 appData.clockMode = TRUE;
1698 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1701 } else if (*appData.cmailGameName != NULLCHAR) {
1702 /* Set up cmail mode */
1703 ReloadCmailMsgEvent(TRUE);
1705 /* Set up other modes */
1706 if (initialMode == AnalyzeFile) {
1707 if (*appData.loadGameFile == NULLCHAR) {
1708 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1712 if (*appData.loadGameFile != NULLCHAR) {
1713 (void) LoadGameFromFile(appData.loadGameFile,
1714 appData.loadGameIndex,
1715 appData.loadGameFile, TRUE);
1716 } else if (*appData.loadPositionFile != NULLCHAR) {
1717 (void) LoadPositionFromFile(appData.loadPositionFile,
1718 appData.loadPositionIndex,
1719 appData.loadPositionFile);
1720 /* [HGM] try to make self-starting even after FEN load */
1721 /* to allow automatic setup of fairy variants with wtm */
1722 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1723 gameMode = BeginningOfGame;
1724 setboardSpoiledMachineBlack = 1;
1726 /* [HGM] loadPos: make that every new game uses the setup */
1727 /* from file as long as we do not switch variant */
1728 if(!blackPlaysFirst) {
1729 startedFromPositionFile = TRUE;
1730 CopyBoard(filePosition, boards[0]);
1733 if (initialMode == AnalyzeMode) {
1734 if (appData.noChessProgram) {
1735 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1738 if (appData.icsActive) {
1739 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1743 } else if (initialMode == AnalyzeFile) {
1744 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1745 ShowThinkingEvent();
1747 AnalysisPeriodicEvent(1);
1748 } else if (initialMode == MachinePlaysWhite) {
1749 if (appData.noChessProgram) {
1750 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1754 if (appData.icsActive) {
1755 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1759 MachineWhiteEvent();
1760 } else if (initialMode == MachinePlaysBlack) {
1761 if (appData.noChessProgram) {
1762 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1766 if (appData.icsActive) {
1767 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1771 MachineBlackEvent();
1772 } else if (initialMode == TwoMachinesPlay) {
1773 if (appData.noChessProgram) {
1774 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1778 if (appData.icsActive) {
1779 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1784 } else if (initialMode == EditGame) {
1786 } else if (initialMode == EditPosition) {
1787 EditPositionEvent();
1788 } else if (initialMode == Training) {
1789 if (*appData.loadGameFile == NULLCHAR) {
1790 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1799 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1801 DisplayBook(current+1);
1803 MoveHistorySet( movelist, first, last, current, pvInfoList );
1805 EvalGraphSet( first, last, current, pvInfoList );
1807 MakeEngineOutputTitle();
1811 * Establish will establish a contact to a remote host.port.
1812 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1813 * used to talk to the host.
1814 * Returns 0 if okay, error code if not.
1821 if (*appData.icsCommPort != NULLCHAR) {
1822 /* Talk to the host through a serial comm port */
1823 return OpenCommPort(appData.icsCommPort, &icsPR);
1825 } else if (*appData.gateway != NULLCHAR) {
1826 if (*appData.remoteShell == NULLCHAR) {
1827 /* Use the rcmd protocol to run telnet program on a gateway host */
1828 snprintf(buf, sizeof(buf), "%s %s %s",
1829 appData.telnetProgram, appData.icsHost, appData.icsPort);
1830 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1833 /* Use the rsh program to run telnet program on a gateway host */
1834 if (*appData.remoteUser == NULLCHAR) {
1835 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1836 appData.gateway, appData.telnetProgram,
1837 appData.icsHost, appData.icsPort);
1839 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1840 appData.remoteShell, appData.gateway,
1841 appData.remoteUser, appData.telnetProgram,
1842 appData.icsHost, appData.icsPort);
1844 return StartChildProcess(buf, "", &icsPR);
1847 } else if (appData.useTelnet) {
1848 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1851 /* TCP socket interface differs somewhat between
1852 Unix and NT; handle details in the front end.
1854 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1859 EscapeExpand (char *p, char *q)
1860 { // [HGM] initstring: routine to shape up string arguments
1861 while(*p++ = *q++) if(p[-1] == '\\')
1863 case 'n': p[-1] = '\n'; break;
1864 case 'r': p[-1] = '\r'; break;
1865 case 't': p[-1] = '\t'; break;
1866 case '\\': p[-1] = '\\'; break;
1867 case 0: *p = 0; return;
1868 default: p[-1] = q[-1]; break;
1873 show_bytes (FILE *fp, char *buf, int count)
1876 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1877 fprintf(fp, "\\%03o", *buf & 0xff);
1886 /* Returns an errno value */
1888 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1890 char buf[8192], *p, *q, *buflim;
1891 int left, newcount, outcount;
1893 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1894 *appData.gateway != NULLCHAR) {
1895 if (appData.debugMode) {
1896 fprintf(debugFP, ">ICS: ");
1897 show_bytes(debugFP, message, count);
1898 fprintf(debugFP, "\n");
1900 return OutputToProcess(pr, message, count, outError);
1903 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1910 if (appData.debugMode) {
1911 fprintf(debugFP, ">ICS: ");
1912 show_bytes(debugFP, buf, newcount);
1913 fprintf(debugFP, "\n");
1915 outcount = OutputToProcess(pr, buf, newcount, outError);
1916 if (outcount < newcount) return -1; /* to be sure */
1923 } else if (((unsigned char) *p) == TN_IAC) {
1924 *q++ = (char) TN_IAC;
1931 if (appData.debugMode) {
1932 fprintf(debugFP, ">ICS: ");
1933 show_bytes(debugFP, buf, newcount);
1934 fprintf(debugFP, "\n");
1936 outcount = OutputToProcess(pr, buf, newcount, outError);
1937 if (outcount < newcount) return -1; /* to be sure */
1942 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1944 int outError, outCount;
1945 static int gotEof = 0;
1948 /* Pass data read from player on to ICS */
1951 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1952 if (outCount < count) {
1953 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1955 if(have_sent_ICS_logon == 2) {
1956 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1957 fprintf(ini, "%s", message);
1958 have_sent_ICS_logon = 3;
1960 have_sent_ICS_logon = 1;
1961 } else if(have_sent_ICS_logon == 3) {
1962 fprintf(ini, "%s", message);
1964 have_sent_ICS_logon = 1;
1966 } else if (count < 0) {
1967 RemoveInputSource(isr);
1968 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1969 } else if (gotEof++ > 0) {
1970 RemoveInputSource(isr);
1971 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1977 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1978 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1979 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1980 SendToICS("date\n");
1981 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1984 /* added routine for printf style output to ics */
1986 ics_printf (char *format, ...)
1988 char buffer[MSG_SIZ];
1991 va_start(args, format);
1992 vsnprintf(buffer, sizeof(buffer), format, args);
1993 buffer[sizeof(buffer)-1] = '\0';
2001 int count, outCount, outError;
2003 if (icsPR == NoProc) return;
2006 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2007 if (outCount < count) {
2008 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2012 /* This is used for sending logon scripts to the ICS. Sending
2013 without a delay causes problems when using timestamp on ICC
2014 (at least on my machine). */
2016 SendToICSDelayed (char *s, long msdelay)
2018 int count, outCount, outError;
2020 if (icsPR == NoProc) return;
2023 if (appData.debugMode) {
2024 fprintf(debugFP, ">ICS: ");
2025 show_bytes(debugFP, s, count);
2026 fprintf(debugFP, "\n");
2028 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2030 if (outCount < count) {
2031 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2036 /* Remove all highlighting escape sequences in s
2037 Also deletes any suffix starting with '('
2040 StripHighlightAndTitle (char *s)
2042 static char retbuf[MSG_SIZ];
2045 while (*s != NULLCHAR) {
2046 while (*s == '\033') {
2047 while (*s != NULLCHAR && !isalpha(*s)) s++;
2048 if (*s != NULLCHAR) s++;
2050 while (*s != NULLCHAR && *s != '\033') {
2051 if (*s == '(' || *s == '[') {
2062 /* Remove all highlighting escape sequences in s */
2064 StripHighlight (char *s)
2066 static char retbuf[MSG_SIZ];
2069 while (*s != NULLCHAR) {
2070 while (*s == '\033') {
2071 while (*s != NULLCHAR && !isalpha(*s)) s++;
2072 if (*s != NULLCHAR) s++;
2074 while (*s != NULLCHAR && *s != '\033') {
2082 char engineVariant[MSG_SIZ];
2083 char *variantNames[] = VARIANT_NAMES;
2085 VariantName (VariantClass v)
2087 if(v == VariantUnknown || *engineVariant) return engineVariant;
2088 return variantNames[v];
2092 /* Identify a variant from the strings the chess servers use or the
2093 PGN Variant tag names we use. */
2095 StringToVariant (char *e)
2099 VariantClass v = VariantNormal;
2100 int i, found = FALSE;
2106 /* [HGM] skip over optional board-size prefixes */
2107 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2108 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2109 while( *e++ != '_');
2112 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2116 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2117 if (p = StrCaseStr(e, variantNames[i])) {
2118 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2119 v = (VariantClass) i;
2126 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2127 || StrCaseStr(e, "wild/fr")
2128 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2129 v = VariantFischeRandom;
2130 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2131 (i = 1, p = StrCaseStr(e, "w"))) {
2133 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2140 case 0: /* FICS only, actually */
2142 /* Castling legal even if K starts on d-file */
2143 v = VariantWildCastle;
2148 /* Castling illegal even if K & R happen to start in
2149 normal positions. */
2150 v = VariantNoCastle;
2163 /* Castling legal iff K & R start in normal positions */
2169 /* Special wilds for position setup; unclear what to do here */
2170 v = VariantLoadable;
2173 /* Bizarre ICC game */
2174 v = VariantTwoKings;
2177 v = VariantKriegspiel;
2183 v = VariantFischeRandom;
2186 v = VariantCrazyhouse;
2189 v = VariantBughouse;
2195 /* Not quite the same as FICS suicide! */
2196 v = VariantGiveaway;
2202 v = VariantShatranj;
2205 /* Temporary names for future ICC types. The name *will* change in
2206 the next xboard/WinBoard release after ICC defines it. */
2244 v = VariantCapablanca;
2247 v = VariantKnightmate;
2253 v = VariantCylinder;
2259 v = VariantCapaRandom;
2262 v = VariantBerolina;
2274 /* Found "wild" or "w" in the string but no number;
2275 must assume it's normal chess. */
2279 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2280 if( (len >= MSG_SIZ) && appData.debugMode )
2281 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2283 DisplayError(buf, 0);
2289 if (appData.debugMode) {
2290 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2291 e, wnum, VariantName(v));
2296 static int leftover_start = 0, leftover_len = 0;
2297 char star_match[STAR_MATCH_N][MSG_SIZ];
2299 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2300 advance *index beyond it, and set leftover_start to the new value of
2301 *index; else return FALSE. If pattern contains the character '*', it
2302 matches any sequence of characters not containing '\r', '\n', or the
2303 character following the '*' (if any), and the matched sequence(s) are
2304 copied into star_match.
2307 looking_at ( char *buf, int *index, char *pattern)
2309 char *bufp = &buf[*index], *patternp = pattern;
2311 char *matchp = star_match[0];
2314 if (*patternp == NULLCHAR) {
2315 *index = leftover_start = bufp - buf;
2319 if (*bufp == NULLCHAR) return FALSE;
2320 if (*patternp == '*') {
2321 if (*bufp == *(patternp + 1)) {
2323 matchp = star_match[++star_count];
2327 } else if (*bufp == '\n' || *bufp == '\r') {
2329 if (*patternp == NULLCHAR)
2334 *matchp++ = *bufp++;
2338 if (*patternp != *bufp) return FALSE;
2345 SendToPlayer (char *data, int length)
2347 int error, outCount;
2348 outCount = OutputToProcess(NoProc, data, length, &error);
2349 if (outCount < length) {
2350 DisplayFatalError(_("Error writing to display"), error, 1);
2355 PackHolding (char packed[], char *holding)
2365 switch (runlength) {
2376 sprintf(q, "%d", runlength);
2388 /* Telnet protocol requests from the front end */
2390 TelnetRequest (unsigned char ddww, unsigned char option)
2392 unsigned char msg[3];
2393 int outCount, outError;
2395 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2397 if (appData.debugMode) {
2398 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2414 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2423 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2426 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2431 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2433 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2440 if (!appData.icsActive) return;
2441 TelnetRequest(TN_DO, TN_ECHO);
2447 if (!appData.icsActive) return;
2448 TelnetRequest(TN_DONT, TN_ECHO);
2452 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2454 /* put the holdings sent to us by the server on the board holdings area */
2455 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2459 if(gameInfo.holdingsWidth < 2) return;
2460 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2461 return; // prevent overwriting by pre-board holdings
2463 if( (int)lowestPiece >= BlackPawn ) {
2466 holdingsStartRow = BOARD_HEIGHT-1;
2469 holdingsColumn = BOARD_WIDTH-1;
2470 countsColumn = BOARD_WIDTH-2;
2471 holdingsStartRow = 0;
2475 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2476 board[i][holdingsColumn] = EmptySquare;
2477 board[i][countsColumn] = (ChessSquare) 0;
2479 while( (p=*holdings++) != NULLCHAR ) {
2480 piece = CharToPiece( ToUpper(p) );
2481 if(piece == EmptySquare) continue;
2482 /*j = (int) piece - (int) WhitePawn;*/
2483 j = PieceToNumber(piece);
2484 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2485 if(j < 0) continue; /* should not happen */
2486 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2487 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2488 board[holdingsStartRow+j*direction][countsColumn]++;
2494 VariantSwitch (Board board, VariantClass newVariant)
2496 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2497 static Board oldBoard;
2499 startedFromPositionFile = FALSE;
2500 if(gameInfo.variant == newVariant) return;
2502 /* [HGM] This routine is called each time an assignment is made to
2503 * gameInfo.variant during a game, to make sure the board sizes
2504 * are set to match the new variant. If that means adding or deleting
2505 * holdings, we shift the playing board accordingly
2506 * This kludge is needed because in ICS observe mode, we get boards
2507 * of an ongoing game without knowing the variant, and learn about the
2508 * latter only later. This can be because of the move list we requested,
2509 * in which case the game history is refilled from the beginning anyway,
2510 * but also when receiving holdings of a crazyhouse game. In the latter
2511 * case we want to add those holdings to the already received position.
2515 if (appData.debugMode) {
2516 fprintf(debugFP, "Switch board from %s to %s\n",
2517 VariantName(gameInfo.variant), VariantName(newVariant));
2518 setbuf(debugFP, NULL);
2520 shuffleOpenings = 0; /* [HGM] shuffle */
2521 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2525 newWidth = 9; newHeight = 9;
2526 gameInfo.holdingsSize = 7;
2527 case VariantBughouse:
2528 case VariantCrazyhouse:
2529 newHoldingsWidth = 2; break;
2533 newHoldingsWidth = 2;
2534 gameInfo.holdingsSize = 8;
2537 case VariantCapablanca:
2538 case VariantCapaRandom:
2541 newHoldingsWidth = gameInfo.holdingsSize = 0;
2544 if(newWidth != gameInfo.boardWidth ||
2545 newHeight != gameInfo.boardHeight ||
2546 newHoldingsWidth != gameInfo.holdingsWidth ) {
2548 /* shift position to new playing area, if needed */
2549 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2550 for(i=0; i<BOARD_HEIGHT; i++)
2551 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2552 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2554 for(i=0; i<newHeight; i++) {
2555 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2556 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2558 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2559 for(i=0; i<BOARD_HEIGHT; i++)
2560 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2561 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2564 board[HOLDINGS_SET] = 0;
2565 gameInfo.boardWidth = newWidth;
2566 gameInfo.boardHeight = newHeight;
2567 gameInfo.holdingsWidth = newHoldingsWidth;
2568 gameInfo.variant = newVariant;
2569 InitDrawingSizes(-2, 0);
2570 } else gameInfo.variant = newVariant;
2571 CopyBoard(oldBoard, board); // remember correctly formatted board
2572 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2573 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2576 static int loggedOn = FALSE;
2578 /*-- Game start info cache: --*/
2580 char gs_kind[MSG_SIZ];
2581 static char player1Name[128] = "";
2582 static char player2Name[128] = "";
2583 static char cont_seq[] = "\n\\ ";
2584 static int player1Rating = -1;
2585 static int player2Rating = -1;
2586 /*----------------------------*/
2588 ColorClass curColor = ColorNormal;
2589 int suppressKibitz = 0;
2592 Boolean soughtPending = FALSE;
2593 Boolean seekGraphUp;
2594 #define MAX_SEEK_ADS 200
2596 char *seekAdList[MAX_SEEK_ADS];
2597 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2598 float tcList[MAX_SEEK_ADS];
2599 char colorList[MAX_SEEK_ADS];
2600 int nrOfSeekAds = 0;
2601 int minRating = 1010, maxRating = 2800;
2602 int hMargin = 10, vMargin = 20, h, w;
2603 extern int squareSize, lineGap;
2608 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2609 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2610 if(r < minRating+100 && r >=0 ) r = minRating+100;
2611 if(r > maxRating) r = maxRating;
2612 if(tc < 1.f) tc = 1.f;
2613 if(tc > 95.f) tc = 95.f;
2614 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2615 y = ((double)r - minRating)/(maxRating - minRating)
2616 * (h-vMargin-squareSize/8-1) + vMargin;
2617 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2618 if(strstr(seekAdList[i], " u ")) color = 1;
2619 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2620 !strstr(seekAdList[i], "bullet") &&
2621 !strstr(seekAdList[i], "blitz") &&
2622 !strstr(seekAdList[i], "standard") ) color = 2;
2623 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2624 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2628 PlotSingleSeekAd (int i)
2634 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2636 char buf[MSG_SIZ], *ext = "";
2637 VariantClass v = StringToVariant(type);
2638 if(strstr(type, "wild")) {
2639 ext = type + 4; // append wild number
2640 if(v == VariantFischeRandom) type = "chess960"; else
2641 if(v == VariantLoadable) type = "setup"; else
2642 type = VariantName(v);
2644 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2645 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2646 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2647 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2648 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2649 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2650 seekNrList[nrOfSeekAds] = nr;
2651 zList[nrOfSeekAds] = 0;
2652 seekAdList[nrOfSeekAds++] = StrSave(buf);
2653 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2658 EraseSeekDot (int i)
2660 int x = xList[i], y = yList[i], d=squareSize/4, k;
2661 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2662 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2663 // now replot every dot that overlapped
2664 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2665 int xx = xList[k], yy = yList[k];
2666 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2667 DrawSeekDot(xx, yy, colorList[k]);
2672 RemoveSeekAd (int nr)
2675 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2677 if(seekAdList[i]) free(seekAdList[i]);
2678 seekAdList[i] = seekAdList[--nrOfSeekAds];
2679 seekNrList[i] = seekNrList[nrOfSeekAds];
2680 ratingList[i] = ratingList[nrOfSeekAds];
2681 colorList[i] = colorList[nrOfSeekAds];
2682 tcList[i] = tcList[nrOfSeekAds];
2683 xList[i] = xList[nrOfSeekAds];
2684 yList[i] = yList[nrOfSeekAds];
2685 zList[i] = zList[nrOfSeekAds];
2686 seekAdList[nrOfSeekAds] = NULL;
2692 MatchSoughtLine (char *line)
2694 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2695 int nr, base, inc, u=0; char dummy;
2697 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2698 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2700 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2701 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2702 // match: compact and save the line
2703 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2713 if(!seekGraphUp) return FALSE;
2714 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2715 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2717 DrawSeekBackground(0, 0, w, h);
2718 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2719 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2720 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2721 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2723 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2726 snprintf(buf, MSG_SIZ, "%d", i);
2727 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2730 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2731 for(i=1; i<100; i+=(i<10?1:5)) {
2732 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2733 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2734 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2736 snprintf(buf, MSG_SIZ, "%d", i);
2737 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2740 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2745 SeekGraphClick (ClickType click, int x, int y, int moving)
2747 static int lastDown = 0, displayed = 0, lastSecond;
2748 if(y < 0) return FALSE;
2749 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2750 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2751 if(!seekGraphUp) return FALSE;
2752 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2753 DrawPosition(TRUE, NULL);
2756 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2757 if(click == Release || moving) return FALSE;
2759 soughtPending = TRUE;
2760 SendToICS(ics_prefix);
2761 SendToICS("sought\n"); // should this be "sought all"?
2762 } else { // issue challenge based on clicked ad
2763 int dist = 10000; int i, closest = 0, second = 0;
2764 for(i=0; i<nrOfSeekAds; i++) {
2765 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2766 if(d < dist) { dist = d; closest = i; }
2767 second += (d - zList[i] < 120); // count in-range ads
2768 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2772 second = (second > 1);
2773 if(displayed != closest || second != lastSecond) {
2774 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2775 lastSecond = second; displayed = closest;
2777 if(click == Press) {
2778 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2781 } // on press 'hit', only show info
2782 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2783 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2784 SendToICS(ics_prefix);
2786 return TRUE; // let incoming board of started game pop down the graph
2787 } else if(click == Release) { // release 'miss' is ignored
2788 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2789 if(moving == 2) { // right up-click
2790 nrOfSeekAds = 0; // refresh graph
2791 soughtPending = TRUE;
2792 SendToICS(ics_prefix);
2793 SendToICS("sought\n"); // should this be "sought all"?
2796 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2797 // press miss or release hit 'pop down' seek graph
2798 seekGraphUp = FALSE;
2799 DrawPosition(TRUE, NULL);
2805 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2807 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2808 #define STARTED_NONE 0
2809 #define STARTED_MOVES 1
2810 #define STARTED_BOARD 2
2811 #define STARTED_OBSERVE 3
2812 #define STARTED_HOLDINGS 4
2813 #define STARTED_CHATTER 5
2814 #define STARTED_COMMENT 6
2815 #define STARTED_MOVES_NOHIDE 7
2817 static int started = STARTED_NONE;
2818 static char parse[20000];
2819 static int parse_pos = 0;
2820 static char buf[BUF_SIZE + 1];
2821 static int firstTime = TRUE, intfSet = FALSE;
2822 static ColorClass prevColor = ColorNormal;
2823 static int savingComment = FALSE;
2824 static int cmatch = 0; // continuation sequence match
2831 int backup; /* [DM] For zippy color lines */
2833 char talker[MSG_SIZ]; // [HGM] chat
2834 int channel, collective=0;
2836 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2838 if (appData.debugMode) {
2840 fprintf(debugFP, "<ICS: ");
2841 show_bytes(debugFP, data, count);
2842 fprintf(debugFP, "\n");
2846 if (appData.debugMode) { int f = forwardMostMove;
2847 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2848 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2849 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2852 /* If last read ended with a partial line that we couldn't parse,
2853 prepend it to the new read and try again. */
2854 if (leftover_len > 0) {
2855 for (i=0; i<leftover_len; i++)
2856 buf[i] = buf[leftover_start + i];
2859 /* copy new characters into the buffer */
2860 bp = buf + leftover_len;
2861 buf_len=leftover_len;
2862 for (i=0; i<count; i++)
2865 if (data[i] == '\r')
2868 // join lines split by ICS?
2869 if (!appData.noJoin)
2872 Joining just consists of finding matches against the
2873 continuation sequence, and discarding that sequence
2874 if found instead of copying it. So, until a match
2875 fails, there's nothing to do since it might be the
2876 complete sequence, and thus, something we don't want
2879 if (data[i] == cont_seq[cmatch])
2882 if (cmatch == strlen(cont_seq))
2884 cmatch = 0; // complete match. just reset the counter
2887 it's possible for the ICS to not include the space
2888 at the end of the last word, making our [correct]
2889 join operation fuse two separate words. the server
2890 does this when the space occurs at the width setting.
2892 if (!buf_len || buf[buf_len-1] != ' ')
2903 match failed, so we have to copy what matched before
2904 falling through and copying this character. In reality,
2905 this will only ever be just the newline character, but
2906 it doesn't hurt to be precise.
2908 strncpy(bp, cont_seq, cmatch);
2920 buf[buf_len] = NULLCHAR;
2921 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2926 while (i < buf_len) {
2927 /* Deal with part of the TELNET option negotiation
2928 protocol. We refuse to do anything beyond the
2929 defaults, except that we allow the WILL ECHO option,
2930 which ICS uses to turn off password echoing when we are
2931 directly connected to it. We reject this option
2932 if localLineEditing mode is on (always on in xboard)
2933 and we are talking to port 23, which might be a real
2934 telnet server that will try to keep WILL ECHO on permanently.
2936 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2937 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2938 unsigned char option;
2940 switch ((unsigned char) buf[++i]) {
2942 if (appData.debugMode)
2943 fprintf(debugFP, "\n<WILL ");
2944 switch (option = (unsigned char) buf[++i]) {
2946 if (appData.debugMode)
2947 fprintf(debugFP, "ECHO ");
2948 /* Reply only if this is a change, according
2949 to the protocol rules. */
2950 if (remoteEchoOption) break;
2951 if (appData.localLineEditing &&
2952 atoi(appData.icsPort) == TN_PORT) {
2953 TelnetRequest(TN_DONT, TN_ECHO);
2956 TelnetRequest(TN_DO, TN_ECHO);
2957 remoteEchoOption = TRUE;
2961 if (appData.debugMode)
2962 fprintf(debugFP, "%d ", option);
2963 /* Whatever this is, we don't want it. */
2964 TelnetRequest(TN_DONT, option);
2969 if (appData.debugMode)
2970 fprintf(debugFP, "\n<WONT ");
2971 switch (option = (unsigned char) buf[++i]) {
2973 if (appData.debugMode)
2974 fprintf(debugFP, "ECHO ");
2975 /* Reply only if this is a change, according
2976 to the protocol rules. */
2977 if (!remoteEchoOption) break;
2979 TelnetRequest(TN_DONT, TN_ECHO);
2980 remoteEchoOption = FALSE;
2983 if (appData.debugMode)
2984 fprintf(debugFP, "%d ", (unsigned char) option);
2985 /* Whatever this is, it must already be turned
2986 off, because we never agree to turn on
2987 anything non-default, so according to the
2988 protocol rules, we don't reply. */
2993 if (appData.debugMode)
2994 fprintf(debugFP, "\n<DO ");
2995 switch (option = (unsigned char) buf[++i]) {
2997 /* Whatever this is, we refuse to do it. */
2998 if (appData.debugMode)
2999 fprintf(debugFP, "%d ", option);
3000 TelnetRequest(TN_WONT, option);
3005 if (appData.debugMode)
3006 fprintf(debugFP, "\n<DONT ");
3007 switch (option = (unsigned char) buf[++i]) {
3009 if (appData.debugMode)
3010 fprintf(debugFP, "%d ", option);
3011 /* Whatever this is, we are already not doing
3012 it, because we never agree to do anything
3013 non-default, so according to the protocol
3014 rules, we don't reply. */
3019 if (appData.debugMode)
3020 fprintf(debugFP, "\n<IAC ");
3021 /* Doubled IAC; pass it through */
3025 if (appData.debugMode)
3026 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3027 /* Drop all other telnet commands on the floor */
3030 if (oldi > next_out)
3031 SendToPlayer(&buf[next_out], oldi - next_out);
3037 /* OK, this at least will *usually* work */
3038 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3042 if (loggedOn && !intfSet) {
3043 if (ics_type == ICS_ICC) {
3044 snprintf(str, MSG_SIZ,
3045 "/set-quietly interface %s\n/set-quietly style 12\n",
3047 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3048 strcat(str, "/set-2 51 1\n/set seek 1\n");
3049 } else if (ics_type == ICS_CHESSNET) {
3050 snprintf(str, MSG_SIZ, "/style 12\n");
3052 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3053 strcat(str, programVersion);
3054 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3055 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3056 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3058 strcat(str, "$iset nohighlight 1\n");
3060 strcat(str, "$iset lock 1\n$style 12\n");
3063 NotifyFrontendLogin();
3067 if (started == STARTED_COMMENT) {
3068 /* Accumulate characters in comment */
3069 parse[parse_pos++] = buf[i];
3070 if (buf[i] == '\n') {
3071 parse[parse_pos] = NULLCHAR;
3072 if(chattingPartner>=0) {
3074 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3075 OutputChatMessage(chattingPartner, mess);
3076 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3078 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3079 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3080 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3081 OutputChatMessage(p, mess);
3085 chattingPartner = -1;
3086 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3089 if(!suppressKibitz) // [HGM] kibitz
3090 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3091 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3092 int nrDigit = 0, nrAlph = 0, j;
3093 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3094 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3095 parse[parse_pos] = NULLCHAR;
3096 // try to be smart: if it does not look like search info, it should go to
3097 // ICS interaction window after all, not to engine-output window.
3098 for(j=0; j<parse_pos; j++) { // count letters and digits
3099 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3100 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3101 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3103 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3104 int depth=0; float score;
3105 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3106 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3107 pvInfoList[forwardMostMove-1].depth = depth;
3108 pvInfoList[forwardMostMove-1].score = 100*score;
3110 OutputKibitz(suppressKibitz, parse);
3113 if(gameMode == IcsObserving) // restore original ICS messages
3114 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3115 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3117 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3118 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3119 SendToPlayer(tmp, strlen(tmp));
3121 next_out = i+1; // [HGM] suppress printing in ICS window
3123 started = STARTED_NONE;
3125 /* Don't match patterns against characters in comment */
3130 if (started == STARTED_CHATTER) {
3131 if (buf[i] != '\n') {
3132 /* Don't match patterns against characters in chatter */
3136 started = STARTED_NONE;
3137 if(suppressKibitz) next_out = i+1;
3140 /* Kludge to deal with rcmd protocol */
3141 if (firstTime && looking_at(buf, &i, "\001*")) {
3142 DisplayFatalError(&buf[1], 0, 1);
3148 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3151 if (appData.debugMode)
3152 fprintf(debugFP, "ics_type %d\n", ics_type);
3155 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3156 ics_type = ICS_FICS;
3158 if (appData.debugMode)
3159 fprintf(debugFP, "ics_type %d\n", ics_type);
3162 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3163 ics_type = ICS_CHESSNET;
3165 if (appData.debugMode)
3166 fprintf(debugFP, "ics_type %d\n", ics_type);
3171 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3172 looking_at(buf, &i, "Logging you in as \"*\"") ||
3173 looking_at(buf, &i, "will be \"*\""))) {
3174 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3178 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3180 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3181 DisplayIcsInteractionTitle(buf);
3182 have_set_title = TRUE;
3185 /* skip finger notes */
3186 if (started == STARTED_NONE &&
3187 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3188 (buf[i] == '1' && buf[i+1] == '0')) &&
3189 buf[i+2] == ':' && buf[i+3] == ' ') {
3190 started = STARTED_CHATTER;
3196 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3197 if(appData.seekGraph) {
3198 if(soughtPending && MatchSoughtLine(buf+i)) {
3199 i = strstr(buf+i, "rated") - buf;
3200 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201 next_out = leftover_start = i;
3202 started = STARTED_CHATTER;
3203 suppressKibitz = TRUE;
3206 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3207 && looking_at(buf, &i, "* ads displayed")) {
3208 soughtPending = FALSE;
3213 if(appData.autoRefresh) {
3214 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3215 int s = (ics_type == ICS_ICC); // ICC format differs
3217 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3218 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3219 looking_at(buf, &i, "*% "); // eat prompt
3220 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3221 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222 next_out = i; // suppress
3225 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3226 char *p = star_match[0];
3228 if(seekGraphUp) RemoveSeekAd(atoi(p));
3229 while(*p && *p++ != ' '); // next
3231 looking_at(buf, &i, "*% "); // eat prompt
3232 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3239 /* skip formula vars */
3240 if (started == STARTED_NONE &&
3241 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3242 started = STARTED_CHATTER;
3247 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3248 if (appData.autoKibitz && started == STARTED_NONE &&
3249 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3250 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3251 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3252 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3253 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3254 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3255 suppressKibitz = TRUE;
3256 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3258 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3259 && (gameMode == IcsPlayingWhite)) ||
3260 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3261 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3262 started = STARTED_CHATTER; // own kibitz we simply discard
3264 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3265 parse_pos = 0; parse[0] = NULLCHAR;
3266 savingComment = TRUE;
3267 suppressKibitz = gameMode != IcsObserving ? 2 :
3268 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3272 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3273 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3274 && atoi(star_match[0])) {
3275 // suppress the acknowledgements of our own autoKibitz
3277 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3279 SendToPlayer(star_match[0], strlen(star_match[0]));
3280 if(looking_at(buf, &i, "*% ")) // eat prompt
3281 suppressKibitz = FALSE;
3285 } // [HGM] kibitz: end of patch
3287 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3289 // [HGM] chat: intercept tells by users for which we have an open chat window
3291 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3292 looking_at(buf, &i, "* whispers:") ||
3293 looking_at(buf, &i, "* kibitzes:") ||
3294 looking_at(buf, &i, "* shouts:") ||
3295 looking_at(buf, &i, "* c-shouts:") ||
3296 looking_at(buf, &i, "--> * ") ||
3297 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3298 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3299 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3300 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3302 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3303 chattingPartner = -1; collective = 0;
3305 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3306 for(p=0; p<MAX_CHAT; p++) {
3308 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3309 talker[0] = '['; strcat(talker, "] ");
3310 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3311 chattingPartner = p; break;
3314 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3315 for(p=0; p<MAX_CHAT; p++) {
3317 if(!strcmp("kibitzes", chatPartner[p])) {
3318 talker[0] = '['; strcat(talker, "] ");
3319 chattingPartner = p; break;
3322 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3323 for(p=0; p<MAX_CHAT; p++) {
3325 if(!strcmp("whispers", chatPartner[p])) {
3326 talker[0] = '['; strcat(talker, "] ");
3327 chattingPartner = p; break;
3330 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3331 if(buf[i-8] == '-' && buf[i-3] == 't')
3332 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3334 if(!strcmp("c-shouts", chatPartner[p])) {
3335 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3336 chattingPartner = p; break;
3339 if(chattingPartner < 0)
3340 for(p=0; p<MAX_CHAT; p++) {
3342 if(!strcmp("shouts", chatPartner[p])) {
3343 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3344 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3345 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3346 chattingPartner = p; break;
3350 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3351 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3353 Colorize(ColorTell, FALSE);
3354 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3356 chattingPartner = p; break;
3358 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3359 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3360 started = STARTED_COMMENT;
3361 parse_pos = 0; parse[0] = NULLCHAR;
3362 savingComment = 3 + chattingPartner; // counts as TRUE
3363 if(collective == 3) i = oldi; else {
3364 suppressKibitz = TRUE;
3365 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3366 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3370 } // [HGM] chat: end of patch
3373 if (appData.zippyTalk || appData.zippyPlay) {
3374 /* [DM] Backup address for color zippy lines */
3376 if (loggedOn == TRUE)
3377 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3378 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3380 } // [DM] 'else { ' deleted
3382 /* Regular tells and says */
3383 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3384 looking_at(buf, &i, "* (your partner) tells you: ") ||
3385 looking_at(buf, &i, "* says: ") ||
3386 /* Don't color "message" or "messages" output */
3387 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3388 looking_at(buf, &i, "*. * at *:*: ") ||
3389 looking_at(buf, &i, "--* (*:*): ") ||
3390 /* Message notifications (same color as tells) */
3391 looking_at(buf, &i, "* has left a message ") ||
3392 looking_at(buf, &i, "* just sent you a message:\n") ||
3393 /* Whispers and kibitzes */
3394 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3395 looking_at(buf, &i, "* kibitzes: ") ||
3397 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3399 if (tkind == 1 && strchr(star_match[0], ':')) {
3400 /* Avoid "tells you:" spoofs in channels */
3403 if (star_match[0][0] == NULLCHAR ||
3404 strchr(star_match[0], ' ') ||
3405 (tkind == 3 && strchr(star_match[1], ' '))) {
3406 /* Reject bogus matches */
3409 if (appData.colorize) {
3410 if (oldi > next_out) {
3411 SendToPlayer(&buf[next_out], oldi - next_out);
3416 Colorize(ColorTell, FALSE);
3417 curColor = ColorTell;
3420 Colorize(ColorKibitz, FALSE);
3421 curColor = ColorKibitz;
3424 p = strrchr(star_match[1], '