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, 2015 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
63 # define EGBB_NAME "egbbdll64.dll"
65 # define EGBB_NAME "egbbdll.dll"
70 # include <sys/file.h>
75 # define EGBB_NAME "egbbso64.so"
77 # define EGBB_NAME "egbbso.so"
79 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
81 # define HMODULE void *
82 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 # define GetProcAddress dlsym
93 #include <sys/types.h>
102 #else /* not STDC_HEADERS */
105 # else /* not HAVE_STRING_H */
106 # include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
123 # include <sys/time.h>
129 #if defined(_amigados) && !defined(__GNUC__)
134 extern int gettimeofday(struct timeval *, struct timezone *);
142 #include "frontend.h"
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172 char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174 char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187 /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
272 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border; /* [HGM] width of board rim, needed to size seek graph */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
302 /* States for ics_getting_history */
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
310 /* whosays values for GameEnds */
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
322 /* Different types of move when calling RegisterMove */
324 #define CMAIL_RESIGN 1
326 #define CMAIL_ACCEPT 3
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
333 /* Telnet protocol constants */
344 safeStrCpy (char *dst, const char *src, size_t count)
347 assert( dst != NULL );
348 assert( src != NULL );
351 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352 if( i == count && dst[count-1] != NULLCHAR)
354 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355 if(appData.debugMode)
356 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
362 /* Some compiler can't cast u64 to double
363 * This function do the job for us:
365 * We use the highest bit for cast, this only
366 * works if the highest bit is not
367 * in use (This should not happen)
369 * We used this for all compiler
372 u64ToDouble (u64 value)
375 u64 tmp = value & u64Const(0x7fffffffffffffff);
376 r = (double)(s64)tmp;
377 if (value & u64Const(0x8000000000000000))
378 r += 9.2233720368547758080e18; /* 2^63 */
382 /* Fake up flags for now, as we aren't keeping track of castling
383 availability yet. [HGM] Change of logic: the flag now only
384 indicates the type of castlings allowed by the rule of the game.
385 The actual rights themselves are maintained in the array
386 castlingRights, as part of the game history, and are not probed
392 int flags = F_ALL_CASTLE_OK;
393 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394 switch (gameInfo.variant) {
396 flags &= ~F_ALL_CASTLE_OK;
397 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398 flags |= F_IGNORE_CHECK;
400 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
403 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405 case VariantKriegspiel:
406 flags |= F_KRIEGSPIEL_CAPTURE;
408 case VariantCapaRandom:
409 case VariantFischeRandom:
410 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411 case VariantNoCastle:
412 case VariantShatranj:
417 flags &= ~F_ALL_CASTLE_OK;
420 case VariantChuChess:
422 flags |= F_NULL_MOVE;
427 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
435 [AS] Note: sometimes, the sscanf() function is used to parse the input
436 into a fixed-size buffer. Because of this, we must be prepared to
437 receive strings as long as the size of the input buffer, which is currently
438 set to 4K for Windows and 8K for the rest.
439 So, we must either allocate sufficiently large buffers here, or
440 reduce the size of the input buffer in the input reading part.
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
447 ChessProgramState first, second, pairing;
449 /* premove variables */
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
490 int have_sent_ICS_logon = 0;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
504 /* animateTraining preserves the state of appData.animate
505 * when Training mode is activated. This allows the
506 * response to be animated when appData.animate == TRUE and
507 * appData.animateDragging == TRUE.
509 Boolean animateTraining;
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
518 signed char initialRights[BOARD_FILES];
519 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int initialRulePlies, FENrulePlies;
521 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
542 ChessSquare FIDEArray[2][BOARD_FILES] = {
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546 BlackKing, BlackBishop, BlackKnight, BlackRook }
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553 BlackKing, BlackKing, BlackKnight, BlackRook }
556 ChessSquare KnightmateArray[2][BOARD_FILES] = {
557 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559 { BlackRook, BlackMan, BlackBishop, BlackQueen,
560 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587 { BlackRook, BlackKnight, BlackMan, BlackFerz,
588 BlackKing, BlackMan, BlackKnight, BlackRook }
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594 { BlackRook, BlackKnight, BlackMan, BlackFerz,
595 BlackKing, BlackMan, BlackKnight, BlackRook }
598 ChessSquare lionArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601 { BlackRook, BlackLion, BlackBishop, BlackQueen,
602 BlackKing, BlackBishop, BlackKnight, BlackRook }
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 #define GothicArray CapablancaArray
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 #define FalconArray CapablancaArray
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
712 Board initialPosition;
715 /* Convert str to a rating. Checks for special cases of "----",
717 "++++", etc. Also strips ()'s */
719 string_to_rating (char *str)
721 while(*str && !isdigit(*str)) ++str;
723 return 0; /* One of the special "no rating" cases */
731 /* Init programStats */
732 programStats.movelist[0] = 0;
733 programStats.depth = 0;
734 programStats.nr_moves = 0;
735 programStats.moves_left = 0;
736 programStats.nodes = 0;
737 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
738 programStats.score = 0;
739 programStats.got_only_move = 0;
740 programStats.got_fail = 0;
741 programStats.line_is_book = 0;
746 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747 if (appData.firstPlaysBlack) {
748 first.twoMachinesColor = "black\n";
749 second.twoMachinesColor = "white\n";
751 first.twoMachinesColor = "white\n";
752 second.twoMachinesColor = "black\n";
755 first.other = &second;
756 second.other = &first;
759 if(appData.timeOddsMode) {
760 norm = appData.timeOdds[0];
761 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763 first.timeOdds = appData.timeOdds[0]/norm;
764 second.timeOdds = appData.timeOdds[1]/norm;
767 if(programVersion) free(programVersion);
768 if (appData.noChessProgram) {
769 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770 sprintf(programVersion, "%s", PACKAGE_STRING);
772 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
779 UnloadEngine (ChessProgramState *cps)
781 /* Kill off first chess program */
782 if (cps->isr != NULL)
783 RemoveInputSource(cps->isr);
786 if (cps->pr != NoProc) {
788 DoSleep( appData.delayBeforeQuit );
789 SendToProgram("quit\n", cps);
790 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
797 ClearOptions (ChessProgramState *cps)
800 cps->nrOptions = cps->comboCnt = 0;
801 for(i=0; i<MAX_OPTIONS; i++) {
802 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803 cps->option[i].textValue = 0;
807 char *engineNames[] = {
808 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
817 InitEngine (ChessProgramState *cps, int n)
818 { // [HGM] all engine initialiation put in a function that does one engine
822 cps->which = engineNames[n];
823 cps->maybeThinking = FALSE;
827 cps->sendDrawOffers = 1;
829 cps->program = appData.chessProgram[n];
830 cps->host = appData.host[n];
831 cps->dir = appData.directory[n];
832 cps->initString = appData.engInitString[n];
833 cps->computerString = appData.computerString[n];
834 cps->useSigint = TRUE;
835 cps->useSigterm = TRUE;
836 cps->reuse = appData.reuse[n];
837 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
838 cps->useSetboard = FALSE;
840 cps->usePing = FALSE;
843 cps->usePlayother = FALSE;
844 cps->useColors = TRUE;
845 cps->useUsermove = FALSE;
846 cps->sendICS = FALSE;
847 cps->sendName = appData.icsActive;
848 cps->sdKludge = FALSE;
849 cps->stKludge = FALSE;
850 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851 TidyProgramName(cps->program, cps->host, cps->tidy);
853 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854 cps->analysisSupport = 2; /* detect */
855 cps->analyzing = FALSE;
856 cps->initDone = FALSE;
858 cps->pseudo = appData.pseudo[n];
860 /* New features added by Tord: */
861 cps->useFEN960 = FALSE;
862 cps->useOOCastle = TRUE;
863 /* End of new features added by Tord. */
864 cps->fenOverride = appData.fenOverride[n];
866 /* [HGM] time odds: set factor for each machine */
867 cps->timeOdds = appData.timeOdds[n];
869 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870 cps->accumulateTC = appData.accumulateTC[n];
871 cps->maxNrOfSessions = 1;
876 cps->drawDepth = appData.drawDepth[n];
877 cps->supportsNPS = UNKNOWN;
878 cps->memSize = FALSE;
879 cps->maxCores = FALSE;
880 ASSIGN(cps->egtFormats, "");
883 cps->optionSettings = appData.engOptions[n];
885 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886 cps->isUCI = appData.isUCI[n]; /* [AS] */
887 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890 if (appData.protocolVersion[n] > PROTOVER
891 || appData.protocolVersion[n] < 1)
896 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897 appData.protocolVersion[n]);
898 if( (len >= MSG_SIZ) && appData.debugMode )
899 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901 DisplayFatalError(buf, 0, 2);
905 cps->protocolVersion = appData.protocolVersion[n];
908 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
909 ParseFeatures(appData.featureDefaults, cps);
912 ChessProgramState *savCps;
920 if(WaitForEngine(savCps, LoadEngine)) return;
921 CommonEngineInit(); // recalculate time odds
922 if(gameInfo.variant != StringToVariant(appData.variant)) {
923 // we changed variant when loading the engine; this forces us to reset
924 Reset(TRUE, savCps != &first);
925 oldMode = BeginningOfGame; // to prevent restoring old mode
927 InitChessProgram(savCps, FALSE);
928 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929 DisplayMessage("", "");
930 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
938 ReplaceEngine (ChessProgramState *cps, int n)
940 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942 if(oldMode != BeginningOfGame) EditGameEvent();
945 appData.noChessProgram = FALSE;
946 appData.clockMode = TRUE;
949 if(n) return; // only startup first engine immediately; second can wait
950 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957 static char resetOptions[] =
958 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964 FloatToFront(char **list, char *engineLine)
966 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968 if(appData.recentEngines <= 0) return;
969 TidyProgramName(engineLine, "localhost", tidy+1);
970 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971 strncpy(buf+1, *list, MSG_SIZ-50);
972 if(p = strstr(buf, tidy)) { // tidy name appears in list
973 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974 while(*p++ = *++q); // squeeze out
976 strcat(tidy, buf+1); // put list behind tidy name
977 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979 ASSIGN(*list, tidy+1);
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
985 Load (ChessProgramState *cps, int i)
987 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993 appData.firstProtocolVersion = PROTOVER;
994 ParseArgsFromString(buf);
996 ReplaceEngine(cps, i);
997 FloatToFront(&appData.recentEngineList, engineLine);
1001 while(q = strchr(p, SLASH)) p = q+1;
1002 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003 if(engineDir[0] != NULLCHAR) {
1004 ASSIGN(appData.directory[i], engineDir); p = engineName;
1005 } else if(p != engineName) { // derive directory from engine path, when not given
1007 ASSIGN(appData.directory[i], engineName);
1009 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010 } else { ASSIGN(appData.directory[i], "."); }
1011 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014 snprintf(command, MSG_SIZ, "%s %s", p, params);
1017 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018 ASSIGN(appData.chessProgram[i], p);
1019 appData.isUCI[i] = isUCI;
1020 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021 appData.hasOwnBookUCI[i] = hasBook;
1022 if(!nickName[0]) useNick = FALSE;
1023 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1027 q = firstChessProgramNames;
1028 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031 quote, p, quote, appData.directory[i],
1032 useNick ? " -fn \"" : "",
1033 useNick ? nickName : "",
1034 useNick ? "\"" : "",
1035 v1 ? " -firstProtocolVersion 1" : "",
1036 hasBook ? "" : " -fNoOwnBookUCI",
1037 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038 storeVariant ? " -variant " : "",
1039 storeVariant ? VariantName(gameInfo.variant) : "");
1040 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042 if(insert != q) insert[-1] = NULLCHAR;
1043 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045 FloatToFront(&appData.recentEngineList, buf);
1047 ReplaceEngine(cps, i);
1053 int matched, min, sec;
1055 * Parse timeControl resource
1057 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058 appData.movesPerSession)) {
1060 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061 DisplayFatalError(buf, 0, 2);
1065 * Parse searchTime resource
1067 if (*appData.searchTime != NULLCHAR) {
1068 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070 searchTime = min * 60;
1071 } else if (matched == 2) {
1072 searchTime = min * 60 + sec;
1075 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076 DisplayFatalError(buf, 0, 2);
1085 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088 GetTimeMark(&programStartTime);
1089 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090 appData.seedBase = random() + (random()<<15);
1091 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093 ClearProgramStats();
1094 programStats.ok_to_send = 1;
1095 programStats.seen_stat = 0;
1098 * Initialize game list
1104 * Internet chess server status
1106 if (appData.icsActive) {
1107 appData.matchMode = FALSE;
1108 appData.matchGames = 0;
1110 appData.noChessProgram = !appData.zippyPlay;
1112 appData.zippyPlay = FALSE;
1113 appData.zippyTalk = FALSE;
1114 appData.noChessProgram = TRUE;
1116 if (*appData.icsHelper != NULLCHAR) {
1117 appData.useTelnet = TRUE;
1118 appData.telnetProgram = appData.icsHelper;
1121 appData.zippyTalk = appData.zippyPlay = FALSE;
1124 /* [AS] Initialize pv info list [HGM] and game state */
1128 for( i=0; i<=framePtr; i++ ) {
1129 pvInfoList[i].depth = -1;
1130 boards[i][EP_STATUS] = EP_NONE;
1131 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1137 /* [AS] Adjudication threshold */
1138 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140 InitEngine(&first, 0);
1141 InitEngine(&second, 1);
1144 pairing.which = "pairing"; // pairing engine
1145 pairing.pr = NoProc;
1147 pairing.program = appData.pairingEngine;
1148 pairing.host = "localhost";
1151 if (appData.icsActive) {
1152 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1153 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154 appData.clockMode = FALSE;
1155 first.sendTime = second.sendTime = 0;
1159 /* Override some settings from environment variables, for backward
1160 compatibility. Unfortunately it's not feasible to have the env
1161 vars just set defaults, at least in xboard. Ugh.
1163 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1168 if (!appData.icsActive) {
1172 /* Check for variants that are supported only in ICS mode,
1173 or not at all. Some that are accepted here nevertheless
1174 have bugs; see comments below.
1176 VariantClass variant = StringToVariant(appData.variant);
1178 case VariantBughouse: /* need four players and two boards */
1179 case VariantKriegspiel: /* need to hide pieces and move details */
1180 /* case VariantFischeRandom: (Fabien: moved below) */
1181 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182 if( (len >= MSG_SIZ) && appData.debugMode )
1183 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185 DisplayFatalError(buf, 0, 2);
1188 case VariantUnknown:
1189 case VariantLoadable:
1199 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200 if( (len >= MSG_SIZ) && appData.debugMode )
1201 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203 DisplayFatalError(buf, 0, 2);
1206 case VariantNormal: /* definitely works! */
1207 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1211 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1212 case VariantFairy: /* [HGM] TestLegality definitely off! */
1213 case VariantGothic: /* [HGM] should work */
1214 case VariantCapablanca: /* [HGM] should work */
1215 case VariantCourier: /* [HGM] initial forced moves not implemented */
1216 case VariantShogi: /* [HGM] could still mate with pawn drop */
1217 case VariantChu: /* [HGM] experimental */
1218 case VariantKnightmate: /* [HGM] should work */
1219 case VariantCylinder: /* [HGM] untested */
1220 case VariantFalcon: /* [HGM] untested */
1221 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222 offboard interposition not understood */
1223 case VariantWildCastle: /* pieces not automatically shuffled */
1224 case VariantNoCastle: /* pieces not automatically shuffled */
1225 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226 case VariantLosers: /* should work except for win condition,
1227 and doesn't know captures are mandatory */
1228 case VariantSuicide: /* should work except for win condition,
1229 and doesn't know captures are mandatory */
1230 case VariantGiveaway: /* should work except for win condition,
1231 and doesn't know captures are mandatory */
1232 case VariantTwoKings: /* should work */
1233 case VariantAtomic: /* should work except for win condition */
1234 case Variant3Check: /* should work except for win condition */
1235 case VariantShatranj: /* should work except for all win conditions */
1236 case VariantMakruk: /* should work except for draw countdown */
1237 case VariantASEAN : /* should work except for draw countdown */
1238 case VariantBerolina: /* might work if TestLegality is off */
1239 case VariantCapaRandom: /* should work */
1240 case VariantJanus: /* should work */
1241 case VariantSuper: /* experimental */
1242 case VariantGreat: /* experimental, requires legality testing to be off */
1243 case VariantSChess: /* S-Chess, should work */
1244 case VariantGrand: /* should work */
1245 case VariantSpartan: /* should work */
1246 case VariantLion: /* should work */
1247 case VariantChuChess: /* should work */
1255 NextIntegerFromString (char ** str, long * value)
1260 while( *s == ' ' || *s == '\t' ) {
1266 if( *s >= '0' && *s <= '9' ) {
1267 while( *s >= '0' && *s <= '9' ) {
1268 *value = *value * 10 + (*s - '0');
1281 NextTimeControlFromString (char ** str, long * value)
1284 int result = NextIntegerFromString( str, &temp );
1287 *value = temp * 60; /* Minutes */
1288 if( **str == ':' ) {
1290 result = NextIntegerFromString( str, &temp );
1291 *value += temp; /* Seconds */
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301 int result = -1, type = 0; long temp, temp2;
1303 if(**str != ':') return -1; // old params remain in force!
1305 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306 if( NextIntegerFromString( str, &temp ) ) return -1;
1307 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1310 /* time only: incremental or sudden-death time control */
1311 if(**str == '+') { /* increment follows; read it */
1313 if(**str == '!') type = *(*str)++; // Bronstein TC
1314 if(result = NextIntegerFromString( str, &temp2)) return -1;
1315 *inc = temp2 * 1000;
1316 if(**str == '.') { // read fraction of increment
1317 char *start = ++(*str);
1318 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320 while(start++ < *str) temp2 /= 10;
1324 *moves = 0; *tc = temp * 1000; *incType = type;
1328 (*str)++; /* classical time control */
1329 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 { /* [HGM] get time to add from the multi-session time-control string */
1343 int incType, moves=1; /* kludge to force reading of first session */
1344 long time, increment;
1347 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351 if(movenr == -1) return time; /* last move before new session */
1352 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354 if(!moves) return increment; /* current session is incremental */
1355 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356 } while(movenr >= -1); /* try again for next session */
1358 return 0; // no new time quota on this move
1362 ParseTimeControl (char *tc, float ti, int mps)
1366 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1369 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1375 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1380 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382 snprintf(buf, MSG_SIZ, ":%s", mytc);
1384 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1391 /* Parse second time control */
1394 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1402 timeControl_2 = tc2 * 1000;
1412 timeControl = tc1 * 1000;
1415 timeIncrement = ti * 1000; /* convert to ms */
1416 movesPerSession = 0;
1419 movesPerSession = mps;
1427 if (appData.debugMode) {
1428 # ifdef __GIT_VERSION
1429 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 fprintf(debugFP, "Version: %s\n", programVersion);
1434 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436 set_cont_sequence(appData.wrapContSeq);
1437 if (appData.matchGames > 0) {
1438 appData.matchMode = TRUE;
1439 } else if (appData.matchMode) {
1440 appData.matchGames = 1;
1442 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443 appData.matchGames = appData.sameColorGames;
1444 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1449 if (appData.noChessProgram || first.protocolVersion == 1) {
1452 /* kludge: allow timeout for initial "feature" commands */
1454 DisplayMessage("", _("Starting chess program"));
1455 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1460 CalculateIndex (int index, int gameNr)
1461 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463 if(index > 0) return index; // fixed nmber
1464 if(index == 0) return 1;
1465 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1471 LoadGameOrPosition (int gameNr)
1472 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473 if (*appData.loadGameFile != NULLCHAR) {
1474 if (!LoadGameFromFile(appData.loadGameFile,
1475 CalculateIndex(appData.loadGameIndex, gameNr),
1476 appData.loadGameFile, FALSE)) {
1477 DisplayFatalError(_("Bad game file"), 0, 1);
1480 } else if (*appData.loadPositionFile != NULLCHAR) {
1481 if (!LoadPositionFromFile(appData.loadPositionFile,
1482 CalculateIndex(appData.loadPositionIndex, gameNr),
1483 appData.loadPositionFile)) {
1484 DisplayFatalError(_("Bad position file"), 0, 1);
1492 ReserveGame (int gameNr, char resChar)
1494 FILE *tf = fopen(appData.tourneyFile, "r+");
1495 char *p, *q, c, buf[MSG_SIZ];
1496 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497 safeStrCpy(buf, lastMsg, MSG_SIZ);
1498 DisplayMessage(_("Pick new game"), "");
1499 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500 ParseArgsFromFile(tf);
1501 p = q = appData.results;
1502 if(appData.debugMode) {
1503 char *r = appData.participants;
1504 fprintf(debugFP, "results = '%s'\n", p);
1505 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506 fprintf(debugFP, "\n");
1508 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511 safeStrCpy(q, p, strlen(p) + 2);
1512 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1518 fseek(tf, -(strlen(p)+4), SEEK_END);
1520 if(c != '"') // depending on DOS or Unix line endings we can be one off
1521 fseek(tf, -(strlen(p)+2), SEEK_END);
1522 else fseek(tf, -(strlen(p)+3), SEEK_END);
1523 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524 DisplayMessage(buf, "");
1525 free(p); appData.results = q;
1526 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528 int round = appData.defaultMatchGames * appData.tourneyType;
1529 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1530 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531 UnloadEngine(&first); // next game belongs to other pairing;
1532 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1538 MatchEvent (int mode)
1539 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541 if(matchMode) { // already in match mode: switch it off
1543 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1546 // if(gameMode != BeginningOfGame) {
1547 // DisplayError(_("You can only start a match from the initial position."), 0);
1551 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552 /* Set up machine vs. machine match */
1554 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555 if(appData.tourneyFile[0]) {
1557 if(nextGame > appData.matchGames) {
1559 if(strchr(appData.results, '*') == NULL) {
1561 appData.tourneyCycles++;
1562 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564 NextTourneyGame(-1, &dummy);
1566 if(nextGame <= appData.matchGames) {
1567 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569 ScheduleDelayedEvent(NextMatchGame, 10000);
1574 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575 DisplayError(buf, 0);
1576 appData.tourneyFile[0] = 0;
1580 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1581 DisplayFatalError(_("Can't have a match with no chess programs"),
1586 matchGame = roundNr = 1;
1587 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1594 InitBackEnd3 P((void))
1596 GameMode initialMode;
1600 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1601 !strcmp(appData.variant, "normal") && // no explicit variant request
1602 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1603 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1604 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605 char c, *q = first.variants, *p = strchr(q, ',');
1606 if(p) *p = NULLCHAR;
1607 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612 Reset(TRUE, FALSE); // and re-initialize
1617 InitChessProgram(&first, startedFromSetupPosition);
1619 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1620 free(programVersion);
1621 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1626 if (appData.icsActive) {
1628 /* [DM] Make a console window if needed [HGM] merged ifs */
1634 if (*appData.icsCommPort != NULLCHAR)
1635 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636 appData.icsCommPort);
1638 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639 appData.icsHost, appData.icsPort);
1641 if( (len >= MSG_SIZ) && appData.debugMode )
1642 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644 DisplayFatalError(buf, err, 1);
1649 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654 } else if (appData.noChessProgram) {
1660 if (*appData.cmailGameName != NULLCHAR) {
1662 OpenLoopback(&cmailPR);
1664 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1668 DisplayMessage("", "");
1669 if (StrCaseCmp(appData.initialMode, "") == 0) {
1670 initialMode = BeginningOfGame;
1671 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1677 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678 initialMode = TwoMachinesPlay;
1679 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680 initialMode = AnalyzeFile;
1681 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682 initialMode = AnalyzeMode;
1683 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684 initialMode = MachinePlaysWhite;
1685 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686 initialMode = MachinePlaysBlack;
1687 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688 initialMode = EditGame;
1689 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690 initialMode = EditPosition;
1691 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692 initialMode = Training;
1694 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695 if( (len >= MSG_SIZ) && appData.debugMode )
1696 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698 DisplayFatalError(buf, 0, 2);
1702 if (appData.matchMode) {
1703 if(appData.tourneyFile[0]) { // start tourney from command line
1705 if(f = fopen(appData.tourneyFile, "r")) {
1706 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708 appData.clockMode = TRUE;
1710 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1713 } else if (*appData.cmailGameName != NULLCHAR) {
1714 /* Set up cmail mode */
1715 ReloadCmailMsgEvent(TRUE);
1717 /* Set up other modes */
1718 if (initialMode == AnalyzeFile) {
1719 if (*appData.loadGameFile == NULLCHAR) {
1720 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1724 if (*appData.loadGameFile != NULLCHAR) {
1725 (void) LoadGameFromFile(appData.loadGameFile,
1726 appData.loadGameIndex,
1727 appData.loadGameFile, TRUE);
1728 } else if (*appData.loadPositionFile != NULLCHAR) {
1729 (void) LoadPositionFromFile(appData.loadPositionFile,
1730 appData.loadPositionIndex,
1731 appData.loadPositionFile);
1732 /* [HGM] try to make self-starting even after FEN load */
1733 /* to allow automatic setup of fairy variants with wtm */
1734 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735 gameMode = BeginningOfGame;
1736 setboardSpoiledMachineBlack = 1;
1738 /* [HGM] loadPos: make that every new game uses the setup */
1739 /* from file as long as we do not switch variant */
1740 if(!blackPlaysFirst) {
1741 startedFromPositionFile = TRUE;
1742 CopyBoard(filePosition, boards[0]);
1745 if (initialMode == AnalyzeMode) {
1746 if (appData.noChessProgram) {
1747 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750 if (appData.icsActive) {
1751 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1755 } else if (initialMode == AnalyzeFile) {
1756 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1757 ShowThinkingEvent();
1759 AnalysisPeriodicEvent(1);
1760 } else if (initialMode == MachinePlaysWhite) {
1761 if (appData.noChessProgram) {
1762 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1766 if (appData.icsActive) {
1767 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1771 MachineWhiteEvent();
1772 } else if (initialMode == MachinePlaysBlack) {
1773 if (appData.noChessProgram) {
1774 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1778 if (appData.icsActive) {
1779 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1783 MachineBlackEvent();
1784 } else if (initialMode == TwoMachinesPlay) {
1785 if (appData.noChessProgram) {
1786 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1790 if (appData.icsActive) {
1791 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1796 } else if (initialMode == EditGame) {
1798 } else if (initialMode == EditPosition) {
1799 EditPositionEvent();
1800 } else if (initialMode == Training) {
1801 if (*appData.loadGameFile == NULLCHAR) {
1802 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1811 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 DisplayBook(current+1);
1815 MoveHistorySet( movelist, first, last, current, pvInfoList );
1817 EvalGraphSet( first, last, current, pvInfoList );
1819 MakeEngineOutputTitle();
1823 * Establish will establish a contact to a remote host.port.
1824 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1825 * used to talk to the host.
1826 * Returns 0 if okay, error code if not.
1833 if (*appData.icsCommPort != NULLCHAR) {
1834 /* Talk to the host through a serial comm port */
1835 return OpenCommPort(appData.icsCommPort, &icsPR);
1837 } else if (*appData.gateway != NULLCHAR) {
1838 if (*appData.remoteShell == NULLCHAR) {
1839 /* Use the rcmd protocol to run telnet program on a gateway host */
1840 snprintf(buf, sizeof(buf), "%s %s %s",
1841 appData.telnetProgram, appData.icsHost, appData.icsPort);
1842 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845 /* Use the rsh program to run telnet program on a gateway host */
1846 if (*appData.remoteUser == NULLCHAR) {
1847 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1848 appData.gateway, appData.telnetProgram,
1849 appData.icsHost, appData.icsPort);
1851 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1852 appData.remoteShell, appData.gateway,
1853 appData.remoteUser, appData.telnetProgram,
1854 appData.icsHost, appData.icsPort);
1856 return StartChildProcess(buf, "", &icsPR);
1859 } else if (appData.useTelnet) {
1860 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863 /* TCP socket interface differs somewhat between
1864 Unix and NT; handle details in the front end.
1866 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1871 EscapeExpand (char *p, char *q)
1872 { // [HGM] initstring: routine to shape up string arguments
1873 while(*p++ = *q++) if(p[-1] == '\\')
1875 case 'n': p[-1] = '\n'; break;
1876 case 'r': p[-1] = '\r'; break;
1877 case 't': p[-1] = '\t'; break;
1878 case '\\': p[-1] = '\\'; break;
1879 case 0: *p = 0; return;
1880 default: p[-1] = q[-1]; break;
1885 show_bytes (FILE *fp, char *buf, int count)
1888 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1889 fprintf(fp, "\\%03o", *buf & 0xff);
1898 /* Returns an errno value */
1900 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 char buf[8192], *p, *q, *buflim;
1903 int left, newcount, outcount;
1905 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1906 *appData.gateway != NULLCHAR) {
1907 if (appData.debugMode) {
1908 fprintf(debugFP, ">ICS: ");
1909 show_bytes(debugFP, message, count);
1910 fprintf(debugFP, "\n");
1912 return OutputToProcess(pr, message, count, outError);
1915 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1922 if (appData.debugMode) {
1923 fprintf(debugFP, ">ICS: ");
1924 show_bytes(debugFP, buf, newcount);
1925 fprintf(debugFP, "\n");
1927 outcount = OutputToProcess(pr, buf, newcount, outError);
1928 if (outcount < newcount) return -1; /* to be sure */
1935 } else if (((unsigned char) *p) == TN_IAC) {
1936 *q++ = (char) TN_IAC;
1943 if (appData.debugMode) {
1944 fprintf(debugFP, ">ICS: ");
1945 show_bytes(debugFP, buf, newcount);
1946 fprintf(debugFP, "\n");
1948 outcount = OutputToProcess(pr, buf, newcount, outError);
1949 if (outcount < newcount) return -1; /* to be sure */
1954 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 int outError, outCount;
1957 static int gotEof = 0;
1960 /* Pass data read from player on to ICS */
1963 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1964 if (outCount < count) {
1965 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967 if(have_sent_ICS_logon == 2) {
1968 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1969 fprintf(ini, "%s", message);
1970 have_sent_ICS_logon = 3;
1972 have_sent_ICS_logon = 1;
1973 } else if(have_sent_ICS_logon == 3) {
1974 fprintf(ini, "%s", message);
1976 have_sent_ICS_logon = 1;
1978 } else if (count < 0) {
1979 RemoveInputSource(isr);
1980 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1981 } else if (gotEof++ > 0) {
1982 RemoveInputSource(isr);
1983 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1989 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1990 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1991 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1992 SendToICS("date\n");
1993 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 /* added routine for printf style output to ics */
1998 ics_printf (char *format, ...)
2000 char buffer[MSG_SIZ];
2003 va_start(args, format);
2004 vsnprintf(buffer, sizeof(buffer), format, args);
2005 buffer[sizeof(buffer)-1] = '\0';
2013 int count, outCount, outError;
2015 if (icsPR == NoProc) return;
2018 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2019 if (outCount < count) {
2020 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2024 /* This is used for sending logon scripts to the ICS. Sending
2025 without a delay causes problems when using timestamp on ICC
2026 (at least on my machine). */
2028 SendToICSDelayed (char *s, long msdelay)
2030 int count, outCount, outError;
2032 if (icsPR == NoProc) return;
2035 if (appData.debugMode) {
2036 fprintf(debugFP, ">ICS: ");
2037 show_bytes(debugFP, s, count);
2038 fprintf(debugFP, "\n");
2040 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042 if (outCount < count) {
2043 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2048 /* Remove all highlighting escape sequences in s
2049 Also deletes any suffix starting with '('
2052 StripHighlightAndTitle (char *s)
2054 static char retbuf[MSG_SIZ];
2057 while (*s != NULLCHAR) {
2058 while (*s == '\033') {
2059 while (*s != NULLCHAR && !isalpha(*s)) s++;
2060 if (*s != NULLCHAR) s++;
2062 while (*s != NULLCHAR && *s != '\033') {
2063 if (*s == '(' || *s == '[') {
2074 /* Remove all highlighting escape sequences in s */
2076 StripHighlight (char *s)
2078 static char retbuf[MSG_SIZ];
2081 while (*s != NULLCHAR) {
2082 while (*s == '\033') {
2083 while (*s != NULLCHAR && !isalpha(*s)) s++;
2084 if (*s != NULLCHAR) s++;
2086 while (*s != NULLCHAR && *s != '\033') {
2094 char engineVariant[MSG_SIZ];
2095 char *variantNames[] = VARIANT_NAMES;
2097 VariantName (VariantClass v)
2099 if(v == VariantUnknown || *engineVariant) return engineVariant;
2100 return variantNames[v];
2104 /* Identify a variant from the strings the chess servers use or the
2105 PGN Variant tag names we use. */
2107 StringToVariant (char *e)
2111 VariantClass v = VariantNormal;
2112 int i, found = FALSE;
2113 char buf[MSG_SIZ], c;
2118 /* [HGM] skip over optional board-size prefixes */
2119 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2120 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2121 while( *e++ != '_');
2124 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2128 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2129 if (p = StrCaseStr(e, variantNames[i])) {
2130 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2131 v = (VariantClass) i;
2138 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2139 || StrCaseStr(e, "wild/fr")
2140 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2141 v = VariantFischeRandom;
2142 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2143 (i = 1, p = StrCaseStr(e, "w"))) {
2145 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2152 case 0: /* FICS only, actually */
2154 /* Castling legal even if K starts on d-file */
2155 v = VariantWildCastle;
2160 /* Castling illegal even if K & R happen to start in
2161 normal positions. */
2162 v = VariantNoCastle;
2175 /* Castling legal iff K & R start in normal positions */
2181 /* Special wilds for position setup; unclear what to do here */
2182 v = VariantLoadable;
2185 /* Bizarre ICC game */
2186 v = VariantTwoKings;
2189 v = VariantKriegspiel;
2195 v = VariantFischeRandom;
2198 v = VariantCrazyhouse;
2201 v = VariantBughouse;
2207 /* Not quite the same as FICS suicide! */
2208 v = VariantGiveaway;
2214 v = VariantShatranj;
2217 /* Temporary names for future ICC types. The name *will* change in
2218 the next xboard/WinBoard release after ICC defines it. */
2256 v = VariantCapablanca;
2259 v = VariantKnightmate;
2265 v = VariantCylinder;
2271 v = VariantCapaRandom;
2274 v = VariantBerolina;
2286 /* Found "wild" or "w" in the string but no number;
2287 must assume it's normal chess. */
2291 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2292 if( (len >= MSG_SIZ) && appData.debugMode )
2293 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295 DisplayError(buf, 0);
2301 if (appData.debugMode) {
2302 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2303 e, wnum, VariantName(v));
2308 static int leftover_start = 0, leftover_len = 0;
2309 char star_match[STAR_MATCH_N][MSG_SIZ];
2311 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2312 advance *index beyond it, and set leftover_start to the new value of
2313 *index; else return FALSE. If pattern contains the character '*', it
2314 matches any sequence of characters not containing '\r', '\n', or the
2315 character following the '*' (if any), and the matched sequence(s) are
2316 copied into star_match.
2319 looking_at ( char *buf, int *index, char *pattern)
2321 char *bufp = &buf[*index], *patternp = pattern;
2323 char *matchp = star_match[0];
2326 if (*patternp == NULLCHAR) {
2327 *index = leftover_start = bufp - buf;
2331 if (*bufp == NULLCHAR) return FALSE;
2332 if (*patternp == '*') {
2333 if (*bufp == *(patternp + 1)) {
2335 matchp = star_match[++star_count];
2339 } else if (*bufp == '\n' || *bufp == '\r') {
2341 if (*patternp == NULLCHAR)
2346 *matchp++ = *bufp++;
2350 if (*patternp != *bufp) return FALSE;
2357 SendToPlayer (char *data, int length)
2359 int error, outCount;
2360 outCount = OutputToProcess(NoProc, data, length, &error);
2361 if (outCount < length) {
2362 DisplayFatalError(_("Error writing to display"), error, 1);
2367 PackHolding (char packed[], char *holding)
2377 switch (runlength) {
2388 sprintf(q, "%d", runlength);
2400 /* Telnet protocol requests from the front end */
2402 TelnetRequest (unsigned char ddww, unsigned char option)
2404 unsigned char msg[3];
2405 int outCount, outError;
2407 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409 if (appData.debugMode) {
2410 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2426 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2435 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2443 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2452 if (!appData.icsActive) return;
2453 TelnetRequest(TN_DO, TN_ECHO);
2459 if (!appData.icsActive) return;
2460 TelnetRequest(TN_DONT, TN_ECHO);
2464 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 /* put the holdings sent to us by the server on the board holdings area */
2467 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2471 if(gameInfo.holdingsWidth < 2) return;
2472 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2473 return; // prevent overwriting by pre-board holdings
2475 if( (int)lowestPiece >= BlackPawn ) {
2478 holdingsStartRow = BOARD_HEIGHT-1;
2481 holdingsColumn = BOARD_WIDTH-1;
2482 countsColumn = BOARD_WIDTH-2;
2483 holdingsStartRow = 0;
2487 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2488 board[i][holdingsColumn] = EmptySquare;
2489 board[i][countsColumn] = (ChessSquare) 0;
2491 while( (p=*holdings++) != NULLCHAR ) {
2492 piece = CharToPiece( ToUpper(p) );
2493 if(piece == EmptySquare) continue;
2494 /*j = (int) piece - (int) WhitePawn;*/
2495 j = PieceToNumber(piece);
2496 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2497 if(j < 0) continue; /* should not happen */
2498 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2499 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2500 board[holdingsStartRow+j*direction][countsColumn]++;
2506 VariantSwitch (Board board, VariantClass newVariant)
2508 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2509 static Board oldBoard;
2511 startedFromPositionFile = FALSE;
2512 if(gameInfo.variant == newVariant) return;
2514 /* [HGM] This routine is called each time an assignment is made to
2515 * gameInfo.variant during a game, to make sure the board sizes
2516 * are set to match the new variant. If that means adding or deleting
2517 * holdings, we shift the playing board accordingly
2518 * This kludge is needed because in ICS observe mode, we get boards
2519 * of an ongoing game without knowing the variant, and learn about the
2520 * latter only later. This can be because of the move list we requested,
2521 * in which case the game history is refilled from the beginning anyway,
2522 * but also when receiving holdings of a crazyhouse game. In the latter
2523 * case we want to add those holdings to the already received position.
2527 if (appData.debugMode) {
2528 fprintf(debugFP, "Switch board from %s to %s\n",
2529 VariantName(gameInfo.variant), VariantName(newVariant));
2530 setbuf(debugFP, NULL);
2532 shuffleOpenings = 0; /* [HGM] shuffle */
2533 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2537 newWidth = 9; newHeight = 9;
2538 gameInfo.holdingsSize = 7;
2539 case VariantBughouse:
2540 case VariantCrazyhouse:
2541 newHoldingsWidth = 2; break;
2545 newHoldingsWidth = 2;
2546 gameInfo.holdingsSize = 8;
2549 case VariantCapablanca:
2550 case VariantCapaRandom:
2553 newHoldingsWidth = gameInfo.holdingsSize = 0;
2556 if(newWidth != gameInfo.boardWidth ||
2557 newHeight != gameInfo.boardHeight ||
2558 newHoldingsWidth != gameInfo.holdingsWidth ) {
2560 /* shift position to new playing area, if needed */
2561 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2562 for(i=0; i<BOARD_HEIGHT; i++)
2563 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2564 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566 for(i=0; i<newHeight; i++) {
2567 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2568 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2571 for(i=0; i<BOARD_HEIGHT; i++)
2572 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2573 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576 board[HOLDINGS_SET] = 0;
2577 gameInfo.boardWidth = newWidth;
2578 gameInfo.boardHeight = newHeight;
2579 gameInfo.holdingsWidth = newHoldingsWidth;
2580 gameInfo.variant = newVariant;
2581 InitDrawingSizes(-2, 0);
2582 } else gameInfo.variant = newVariant;
2583 CopyBoard(oldBoard, board); // remember correctly formatted board
2584 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2585 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 static int loggedOn = FALSE;
2590 /*-- Game start info cache: --*/
2592 char gs_kind[MSG_SIZ];
2593 static char player1Name[128] = "";
2594 static char player2Name[128] = "";
2595 static char cont_seq[] = "\n\\ ";
2596 static int player1Rating = -1;
2597 static int player2Rating = -1;
2598 /*----------------------------*/
2600 ColorClass curColor = ColorNormal;
2601 int suppressKibitz = 0;
2604 Boolean soughtPending = FALSE;
2605 Boolean seekGraphUp;
2606 #define MAX_SEEK_ADS 200
2608 char *seekAdList[MAX_SEEK_ADS];
2609 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2610 float tcList[MAX_SEEK_ADS];
2611 char colorList[MAX_SEEK_ADS];
2612 int nrOfSeekAds = 0;
2613 int minRating = 1010, maxRating = 2800;
2614 int hMargin = 10, vMargin = 20, h, w;
2615 extern int squareSize, lineGap;
2620 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2621 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2622 if(r < minRating+100 && r >=0 ) r = minRating+100;
2623 if(r > maxRating) r = maxRating;
2624 if(tc < 1.f) tc = 1.f;
2625 if(tc > 95.f) tc = 95.f;
2626 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2627 y = ((double)r - minRating)/(maxRating - minRating)
2628 * (h-vMargin-squareSize/8-1) + vMargin;
2629 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2630 if(strstr(seekAdList[i], " u ")) color = 1;
2631 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2632 !strstr(seekAdList[i], "bullet") &&
2633 !strstr(seekAdList[i], "blitz") &&
2634 !strstr(seekAdList[i], "standard") ) color = 2;
2635 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2636 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2640 PlotSingleSeekAd (int i)
2646 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2648 char buf[MSG_SIZ], *ext = "";
2649 VariantClass v = StringToVariant(type);
2650 if(strstr(type, "wild")) {
2651 ext = type + 4; // append wild number
2652 if(v == VariantFischeRandom) type = "chess960"; else
2653 if(v == VariantLoadable) type = "setup"; else
2654 type = VariantName(v);
2656 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2657 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2658 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2659 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2660 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2661 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2662 seekNrList[nrOfSeekAds] = nr;
2663 zList[nrOfSeekAds] = 0;
2664 seekAdList[nrOfSeekAds++] = StrSave(buf);
2665 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2670 EraseSeekDot (int i)
2672 int x = xList[i], y = yList[i], d=squareSize/4, k;
2673 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2674 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2675 // now replot every dot that overlapped
2676 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2677 int xx = xList[k], yy = yList[k];
2678 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2679 DrawSeekDot(xx, yy, colorList[k]);
2684 RemoveSeekAd (int nr)
2687 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689 if(seekAdList[i]) free(seekAdList[i]);
2690 seekAdList[i] = seekAdList[--nrOfSeekAds];
2691 seekNrList[i] = seekNrList[nrOfSeekAds];
2692 ratingList[i] = ratingList[nrOfSeekAds];
2693 colorList[i] = colorList[nrOfSeekAds];
2694 tcList[i] = tcList[nrOfSeekAds];
2695 xList[i] = xList[nrOfSeekAds];
2696 yList[i] = yList[nrOfSeekAds];
2697 zList[i] = zList[nrOfSeekAds];
2698 seekAdList[nrOfSeekAds] = NULL;
2704 MatchSoughtLine (char *line)
2706 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2707 int nr, base, inc, u=0; char dummy;
2709 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2710 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2713 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2714 // match: compact and save the line
2715 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2725 if(!seekGraphUp) return FALSE;
2726 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2727 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2729 DrawSeekBackground(0, 0, w, h);
2730 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2731 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2732 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2733 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738 snprintf(buf, MSG_SIZ, "%d", i);
2739 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2743 for(i=1; i<100; i+=(i<10?1:5)) {
2744 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2745 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2746 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748 snprintf(buf, MSG_SIZ, "%d", i);
2749 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2757 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 static int lastDown = 0, displayed = 0, lastSecond;
2760 if(y < 0) return FALSE;
2761 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2762 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2763 if(!seekGraphUp) return FALSE;
2764 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2765 DrawPosition(TRUE, NULL);
2768 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2769 if(click == Release || moving) return FALSE;
2771 soughtPending = TRUE;
2772 SendToICS(ics_prefix);
2773 SendToICS("sought\n"); // should this be "sought all"?
2774 } else { // issue challenge based on clicked ad
2775 int dist = 10000; int i, closest = 0, second = 0;
2776 for(i=0; i<nrOfSeekAds; i++) {
2777 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2778 if(d < dist) { dist = d; closest = i; }
2779 second += (d - zList[i] < 120); // count in-range ads
2780 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2784 second = (second > 1);
2785 if(displayed != closest || second != lastSecond) {
2786 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2787 lastSecond = second; displayed = closest;
2789 if(click == Press) {
2790 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793 } // on press 'hit', only show info
2794 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2795 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2796 SendToICS(ics_prefix);
2798 return TRUE; // let incoming board of started game pop down the graph
2799 } else if(click == Release) { // release 'miss' is ignored
2800 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2801 if(moving == 2) { // right up-click
2802 nrOfSeekAds = 0; // refresh graph
2803 soughtPending = TRUE;
2804 SendToICS(ics_prefix);
2805 SendToICS("sought\n"); // should this be "sought all"?
2808 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2809 // press miss or release hit 'pop down' seek graph
2810 seekGraphUp = FALSE;
2811 DrawPosition(TRUE, NULL);
2817 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2820 #define STARTED_NONE 0
2821 #define STARTED_MOVES 1
2822 #define STARTED_BOARD 2
2823 #define STARTED_OBSERVE 3
2824 #define STARTED_HOLDINGS 4
2825 #define STARTED_CHATTER 5
2826 #define STARTED_COMMENT 6
2827 #define STARTED_MOVES_NOHIDE 7
2829 static int started = STARTED_NONE;
2830 static char parse[20000];
2831 static int parse_pos = 0;
2832 static char buf[BUF_SIZE + 1];
2833 static int firstTime = TRUE, intfSet = FALSE;
2834 static ColorClass prevColor = ColorNormal;
2835 static int savingComment = FALSE;
2836 static int cmatch = 0; // continuation sequence match
2843 int backup; /* [DM] For zippy color lines */
2845 char talker[MSG_SIZ]; // [HGM] chat
2846 int channel, collective=0;
2848 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850 if (appData.debugMode) {
2852 fprintf(debugFP, "<ICS: ");
2853 show_bytes(debugFP, data, count);
2854 fprintf(debugFP, "\n");
2858 if (appData.debugMode) { int f = forwardMostMove;
2859 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2860 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2861 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864 /* If last read ended with a partial line that we couldn't parse,
2865 prepend it to the new read and try again. */
2866 if (leftover_len > 0) {
2867 for (i=0; i<leftover_len; i++)
2868 buf[i] = buf[leftover_start + i];
2871 /* copy new characters into the buffer */
2872 bp = buf + leftover_len;
2873 buf_len=leftover_len;
2874 for (i=0; i<count; i++)
2877 if (data[i] == '\r')
2880 // join lines split by ICS?
2881 if (!appData.noJoin)
2884 Joining just consists of finding matches against the
2885 continuation sequence, and discarding that sequence
2886 if found instead of copying it. So, until a match
2887 fails, there's nothing to do since it might be the
2888 complete sequence, and thus, something we don't want
2891 if (data[i] == cont_seq[cmatch])
2894 if (cmatch == strlen(cont_seq))
2896 cmatch = 0; // complete match. just reset the counter
2899 it's possible for the ICS to not include the space
2900 at the end of the last word, making our [correct]
2901 join operation fuse two separate words. the server
2902 does this when the space occurs at the width setting.
2904 if (!buf_len || buf[buf_len-1] != ' ')
2915 match failed, so we have to copy what matched before
2916 falling through and copying this character. In reality,
2917 this will only ever be just the newline character, but
2918 it doesn't hurt to be precise.
2920 strncpy(bp, cont_seq, cmatch);
2932 buf[buf_len] = NULLCHAR;
2933 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2938 while (i < buf_len) {
2939 /* Deal with part of the TELNET option negotiation
2940 protocol. We refuse to do anything beyond the
2941 defaults, except that we allow the WILL ECHO option,
2942 which ICS uses to turn off password echoing when we are
2943 directly connected to it. We reject this option
2944 if localLineEditing mode is on (always on in xboard)
2945 and we are talking to port 23, which might be a real
2946 telnet server that will try to keep WILL ECHO on permanently.
2948 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2949 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2950 unsigned char option;
2952 switch ((unsigned char) buf[++i]) {
2954 if (appData.debugMode)
2955 fprintf(debugFP, "\n<WILL ");
2956 switch (option = (unsigned char) buf[++i]) {
2958 if (appData.debugMode)
2959 fprintf(debugFP, "ECHO ");
2960 /* Reply only if this is a change, according
2961 to the protocol rules. */
2962 if (remoteEchoOption) break;
2963 if (appData.localLineEditing &&
2964 atoi(appData.icsPort) == TN_PORT) {
2965 TelnetRequest(TN_DONT, TN_ECHO);
2968 TelnetRequest(TN_DO, TN_ECHO);
2969 remoteEchoOption = TRUE;
2973 if (appData.debugMode)
2974 fprintf(debugFP, "%d ", option);
2975 /* Whatever this is, we don't want it. */
2976 TelnetRequest(TN_DONT, option);
2981 if (appData.debugMode)
2982 fprintf(debugFP, "\n<WONT ");
2983 switch (option = (unsigned char) buf[++i]) {
2985 if (appData.debugMode)
2986 fprintf(debugFP, "ECHO ");
2987 /* Reply only if this is a change, according
2988 to the protocol rules. */
2989 if (!remoteEchoOption) break;
2991 TelnetRequest(TN_DONT, TN_ECHO);
2992 remoteEchoOption = FALSE;
2995 if (appData.debugMode)
2996 fprintf(debugFP, "%d ", (unsigned char) option);
2997 /* Whatever this is, it must already be turned
2998 off, because we never agree to turn on
2999 anything non-default, so according to the
3000 protocol rules, we don't reply. */
3005 if (appData.debugMode)
3006 fprintf(debugFP, "\n<DO ");
3007 switch (option = (unsigned char) buf[++i]) {
3009 /* Whatever this is, we refuse to do it. */
3010 if (appData.debugMode)
3011 fprintf(debugFP, "%d ", option);
3012 TelnetRequest(TN_WONT, option);
3017 if (appData.debugMode)
3018 fprintf(debugFP, "\n<DONT ");
3019 switch (option = (unsigned char) buf[++i]) {
3021 if (appData.debugMode)
3022 fprintf(debugFP, "%d ", option);
3023 /* Whatever this is, we are already not doing
3024 it, because we never agree to do anything
3025 non-default, so according to the protocol
3026 rules, we don't reply. */
3031 if (appData.debugMode)
3032 fprintf(debugFP, "\n<IAC ");
3033 /* Doubled IAC; pass it through */
3037 if (appData.debugMode)
3038 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3039 /* Drop all other telnet commands on the floor */
3042 if (oldi > next_out)
3043 SendToPlayer(&buf[next_out], oldi - next_out);
3049 /* OK, this at least will *usually* work */
3050 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3054 if (loggedOn && !intfSet) {
3055 if (ics_type == ICS_ICC) {
3056 snprintf(str, MSG_SIZ,
3057 "/set-quietly interface %s\n/set-quietly style 12\n",
3059 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3060 strcat(str, "/set-2 51 1\n/set seek 1\n");
3061 } else if (ics_type == ICS_CHESSNET) {
3062 snprintf(str, MSG_SIZ, "/style 12\n");
3064 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3065 strcat(str, programVersion);
3066 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3067 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 strcat(str, "$iset nohighlight 1\n");
3072 strcat(str, "$iset lock 1\n$style 12\n");
3075 NotifyFrontendLogin();
3079 if (started == STARTED_COMMENT) {
3080 /* Accumulate characters in comment */
3081 parse[parse_pos++] = buf[i];
3082 if (buf[i] == '\n') {
3083 parse[parse_pos] = NULLCHAR;
3084 if(chattingPartner>=0) {
3086 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3087 OutputChatMessage(chattingPartner, mess);
3088 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3091 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3092 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3093 OutputChatMessage(p, mess);
3097 chattingPartner = -1;
3098 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101 if(!suppressKibitz) // [HGM] kibitz
3102 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3103 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3104 int nrDigit = 0, nrAlph = 0, j;
3105 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3106 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3107 parse[parse_pos] = NULLCHAR;
3108 // try to be smart: if it does not look like search info, it should go to
3109 // ICS interaction window after all, not to engine-output window.
3110 for(j=0; j<parse_pos; j++) { // count letters and digits
3111 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3112 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3113 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3115 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3116 int depth=0; float score;
3117 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3118 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3119 pvInfoList[forwardMostMove-1].depth = depth;
3120 pvInfoList[forwardMostMove-1].score = 100*score;
3122 OutputKibitz(suppressKibitz, parse);
3125 if(gameMode == IcsObserving) // restore original ICS messages
3126 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3127 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3130 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3131 SendToPlayer(tmp, strlen(tmp));
3133 next_out = i+1; // [HGM] suppress printing in ICS window
3135 started = STARTED_NONE;
3137 /* Don't match patterns against characters in comment */
3142 if (started == STARTED_CHATTER) {
3143 if (buf[i] != '\n') {
3144 /* Don't match patterns against characters in chatter */
3148 started = STARTED_NONE;
3149 if(suppressKibitz) next_out = i+1;
3152 /* Kludge to deal with rcmd protocol */
3153 if (firstTime && looking_at(buf, &i, "\001*")) {
3154 DisplayFatalError(&buf[1], 0, 1);
3160 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163 if (appData.debugMode)
3164 fprintf(debugFP, "ics_type %d\n", ics_type);
3167 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3168 ics_type = ICS_FICS;
3170 if (appData.debugMode)
3171 fprintf(debugFP, "ics_type %d\n", ics_type);
3174 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3175 ics_type = ICS_CHESSNET;
3177 if (appData.debugMode)
3178 fprintf(debugFP, "ics_type %d\n", ics_type);
3183 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3184 looking_at(buf, &i, "Logging you in as \"*\"") ||
3185 looking_at(buf, &i, "will be \"*\""))) {
3186 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3190 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3193 DisplayIcsInteractionTitle(buf);
3194 have_set_title = TRUE;
3197 /* skip finger notes */
3198 if (started == STARTED_NONE &&
3199 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3200 (buf[i] == '1' && buf[i+1] == '0')) &&
3201 buf[i+2] == ':' && buf[i+3] == ' ') {
3202 started = STARTED_CHATTER;
3208 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3209 if(appData.seekGraph) {
3210 if(soughtPending && MatchSoughtLine(buf+i)) {
3211 i = strstr(buf+i, "rated") - buf;
3212 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3213 next_out = leftover_start = i;
3214 started = STARTED_CHATTER;
3215 suppressKibitz = TRUE;
3218 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3219 && looking_at(buf, &i, "* ads displayed")) {
3220 soughtPending = FALSE;
3225 if(appData.autoRefresh) {
3226 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3227 int s = (ics_type == ICS_ICC); // ICC format differs
3229 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3230 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3231 looking_at(buf, &i, "*% "); // eat prompt
3232 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3233 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3234 next_out = i; // suppress
3237 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3238 char *p = star_match[0];
3240 if(seekGraphUp) RemoveSeekAd(atoi(p));
3241 while(*p && *p++ != ' '); // next
3243 looking_at(buf, &i, "*% "); // eat prompt
3244 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3251 /* skip formula vars */
3252 if (started == STARTED_NONE &&
3253 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3254 started = STARTED_CHATTER;
3259 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3260 if (appData.autoKibitz && started == STARTED_NONE &&
3261 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3262 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3263 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3264 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3265 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3266 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3267 suppressKibitz = TRUE;
3268 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3271 && (gameMode == IcsPlayingWhite)) ||
3272 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3273 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3274 started = STARTED_CHATTER; // own kibitz we simply discard
3276 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3277 parse_pos = 0; parse[0] = NULLCHAR;
3278 savingComment = TRUE;
3279 suppressKibitz = gameMode != IcsObserving ? 2 :
3280 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3284 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3285 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3286 && atoi(star_match[0])) {
3287 // suppress the acknowledgements of our own autoKibitz
3289 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3290 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3291 SendToPlayer(star_match[0], strlen(star_match[0]));
3292 if(looking_at(buf, &i, "*% ")) // eat prompt
3293 suppressKibitz = FALSE;
3297 } // [HGM] kibitz: end of patch
3299 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301 // [HGM] chat: intercept tells by users for which we have an open chat window
3303 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3304 looking_at(buf, &i, "* whispers:") ||
3305 looking_at(buf, &i, "* kibitzes:") ||
3306 looking_at(buf, &i, "* shouts:") ||
3307 looking_at(buf, &i, "* c-shouts:") ||
3308 looking_at(buf, &i, "--> * ") ||
3309 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3310 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3311 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3312 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3315 chattingPartner = -1; collective = 0;
3317 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3318 for(p=0; p<MAX_CHAT; p++) {
3320 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3321 talker[0] = '['; strcat(talker, "] ");
3322 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3323 chattingPartner = p; break;
3326 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3327 for(p=0; p<MAX_CHAT; p++) {
3329 if(!strcmp("kibitzes", chatPartner[p])) {
3330 talker[0] = '['; strcat(talker, "] ");
3331 chattingPartner = p; break;
3334 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3335 for(p=0; p<MAX_CHAT; p++) {
3337 if(!strcmp("whispers", chatPartner[p])) {
3338 talker[0] = '['; strcat(talker, "] ");
3339 chattingPartner = p; break;
3342 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3343 if(buf[i-8] == '-' && buf[i-3] == 't')
3344 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346 if(!strcmp("c-shouts", chatPartner[p])) {
3347 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3348 chattingPartner = p; break;
3351 if(chattingPartner < 0)
3352 for(p=0; p<MAX_CHAT; p++) {
3354 if(!strcmp("shouts", chatPartner[p])) {
3355 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3356 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3357 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3358 chattingPartner = p; break;
3362 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3363 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365 Colorize(ColorTell, FALSE);
3366 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368 chattingPartner = p; break;
3370 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3371 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3372 started = STARTED_COMMENT;
3373 parse_pos = 0; parse[0] = NULLCHAR;
3374 savingComment = 3 + chattingPartner; // counts as TRUE
3375 if(collective == 3) i = oldi; else {
3376 suppressKibitz = TRUE;
3377 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3378 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3382 } // [HGM] chat: end of patch
3385 if (appData.zippyTalk || appData.zippyPlay) {
3386 /* [DM] Backup address for color zippy lines */
3388 if (loggedOn == TRUE)
3389 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3390 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 } // [DM] 'else { ' deleted
3394 /* Regular tells and says */
3395 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3396 looking_at(buf, &i, "* (your partner) tells you: ") ||
3397 looking_at(buf, &i, "* says: ") ||
3398 /* Don't color "message" or "messages" output */
3399 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3400 looking_at(buf, &i, "*. * at *:*: ") ||
3401 looking_at(buf, &i, "--* (*:*): ") ||
3402 /* Message notifications (same color as tells) */
3403 looking_at(buf, &i, "* has left a message ") ||
3404 looking_at(buf, &i, "* just sent you a message:\n") ||
3405 /* Whispers and kibitzes */
3406 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3407 looking_at(buf, &i, "* kibitzes: ") ||
3409 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411 if (tkind == 1 && strchr(star_match[0], ':')) {
3412 /* Avoid "tells you:" spoofs in channels */
3415 if (star_match[0][0] == NULLCHAR ||
3416 strchr(star_match[0], ' ') ||
3417 (tkind == 3 && strchr(star_match[1], ' '))) {
3418 /* Reject bogus matches */
3421 if (appData.colorize) {
3422 if (oldi > next_out) {
3423 SendToPlayer(&buf[next_out], oldi - next_out);