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, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
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], avoidMove[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];
446 char promoRestrict[MSG_SIZ];
448 ChessProgramState first, second, pairing;
450 /* premove variables */
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
491 int have_sent_ICS_logon = 0;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
505 /* animateTraining preserves the state of appData.animate
506 * when Training mode is activated. This allows the
507 * response to be animated when appData.animate == TRUE and
508 * appData.animateDragging == TRUE.
510 Boolean animateTraining;
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int initialRulePlies, FENrulePlies;
522 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
543 ChessSquare FIDEArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547 BlackKing, BlackBishop, BlackKnight, BlackRook }
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554 BlackKing, BlackKing, BlackKnight, BlackRook }
557 ChessSquare KnightmateArray[2][BOARD_FILES] = {
558 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560 { BlackRook, BlackMan, BlackBishop, BlackQueen,
561 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackMan, BlackFerz,
589 BlackKing, BlackMan, BlackKnight, BlackRook }
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackMan, BlackFerz,
596 BlackKing, BlackMan, BlackKnight, BlackRook }
599 ChessSquare lionArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackLion, BlackBishop, BlackQueen,
603 BlackKing, BlackBishop, BlackKnight, BlackRook }
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
665 #define GothicArray CapablancaArray
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
676 #define FalconArray CapablancaArray
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
713 Board initialPosition;
716 /* Convert str to a rating. Checks for special cases of "----",
718 "++++", etc. Also strips ()'s */
720 string_to_rating (char *str)
722 while(*str && !isdigit(*str)) ++str;
724 return 0; /* One of the special "no rating" cases */
732 /* Init programStats */
733 programStats.movelist[0] = 0;
734 programStats.depth = 0;
735 programStats.nr_moves = 0;
736 programStats.moves_left = 0;
737 programStats.nodes = 0;
738 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
739 programStats.score = 0;
740 programStats.got_only_move = 0;
741 programStats.got_fail = 0;
742 programStats.line_is_book = 0;
747 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748 if (appData.firstPlaysBlack) {
749 first.twoMachinesColor = "black\n";
750 second.twoMachinesColor = "white\n";
752 first.twoMachinesColor = "white\n";
753 second.twoMachinesColor = "black\n";
756 first.other = &second;
757 second.other = &first;
760 if(appData.timeOddsMode) {
761 norm = appData.timeOdds[0];
762 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764 first.timeOdds = appData.timeOdds[0]/norm;
765 second.timeOdds = appData.timeOdds[1]/norm;
768 if(programVersion) free(programVersion);
769 if (appData.noChessProgram) {
770 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771 sprintf(programVersion, "%s", PACKAGE_STRING);
773 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
780 UnloadEngine (ChessProgramState *cps)
782 /* Kill off first chess program */
783 if (cps->isr != NULL)
784 RemoveInputSource(cps->isr);
787 if (cps->pr != NoProc) {
789 DoSleep( appData.delayBeforeQuit );
790 SendToProgram("quit\n", cps);
791 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
794 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
798 ClearOptions (ChessProgramState *cps)
801 cps->nrOptions = cps->comboCnt = 0;
802 for(i=0; i<MAX_OPTIONS; i++) {
803 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804 cps->option[i].textValue = 0;
808 char *engineNames[] = {
809 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
818 InitEngine (ChessProgramState *cps, int n)
819 { // [HGM] all engine initialiation put in a function that does one engine
823 cps->which = engineNames[n];
824 cps->maybeThinking = FALSE;
828 cps->sendDrawOffers = 1;
830 cps->program = appData.chessProgram[n];
831 cps->host = appData.host[n];
832 cps->dir = appData.directory[n];
833 cps->initString = appData.engInitString[n];
834 cps->computerString = appData.computerString[n];
835 cps->useSigint = TRUE;
836 cps->useSigterm = TRUE;
837 cps->reuse = appData.reuse[n];
838 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
839 cps->useSetboard = FALSE;
841 cps->usePing = FALSE;
844 cps->usePlayother = FALSE;
845 cps->useColors = TRUE;
846 cps->useUsermove = FALSE;
847 cps->sendICS = FALSE;
848 cps->sendName = appData.icsActive;
849 cps->sdKludge = FALSE;
850 cps->stKludge = FALSE;
851 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852 TidyProgramName(cps->program, cps->host, cps->tidy);
854 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855 cps->analysisSupport = 2; /* detect */
856 cps->analyzing = FALSE;
857 cps->initDone = FALSE;
859 cps->pseudo = appData.pseudo[n];
861 /* New features added by Tord: */
862 cps->useFEN960 = FALSE;
863 cps->useOOCastle = TRUE;
864 /* End of new features added by Tord. */
865 cps->fenOverride = appData.fenOverride[n];
867 /* [HGM] time odds: set factor for each machine */
868 cps->timeOdds = appData.timeOdds[n];
870 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871 cps->accumulateTC = appData.accumulateTC[n];
872 cps->maxNrOfSessions = 1;
877 cps->drawDepth = appData.drawDepth[n];
878 cps->supportsNPS = UNKNOWN;
879 cps->memSize = FALSE;
880 cps->maxCores = FALSE;
881 ASSIGN(cps->egtFormats, "");
884 cps->optionSettings = appData.engOptions[n];
886 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887 cps->isUCI = appData.isUCI[n]; /* [AS] */
888 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
891 if (appData.protocolVersion[n] > PROTOVER
892 || appData.protocolVersion[n] < 1)
897 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898 appData.protocolVersion[n]);
899 if( (len >= MSG_SIZ) && appData.debugMode )
900 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902 DisplayFatalError(buf, 0, 2);
906 cps->protocolVersion = appData.protocolVersion[n];
909 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
910 ParseFeatures(appData.featureDefaults, cps);
913 ChessProgramState *savCps;
921 if(WaitForEngine(savCps, LoadEngine)) return;
922 CommonEngineInit(); // recalculate time odds
923 if(gameInfo.variant != StringToVariant(appData.variant)) {
924 // we changed variant when loading the engine; this forces us to reset
925 Reset(TRUE, savCps != &first);
926 oldMode = BeginningOfGame; // to prevent restoring old mode
928 InitChessProgram(savCps, FALSE);
929 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930 DisplayMessage("", "");
931 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
935 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
939 ReplaceEngine (ChessProgramState *cps, int n)
941 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943 if(oldMode != BeginningOfGame) EditGameEvent();
946 appData.noChessProgram = FALSE;
947 appData.clockMode = TRUE;
950 if(n) return; // only startup first engine immediately; second can wait
951 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958 static char resetOptions[] =
959 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
965 FloatToFront(char **list, char *engineLine)
967 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969 if(appData.recentEngines <= 0) return;
970 TidyProgramName(engineLine, "localhost", tidy+1);
971 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972 strncpy(buf+1, *list, MSG_SIZ-50);
973 if(p = strstr(buf, tidy)) { // tidy name appears in list
974 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975 while(*p++ = *++q); // squeeze out
977 strcat(tidy, buf+1); // put list behind tidy name
978 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980 ASSIGN(*list, tidy+1);
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
986 Load (ChessProgramState *cps, int i)
988 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994 appData.firstProtocolVersion = PROTOVER;
995 ParseArgsFromString(buf);
997 ReplaceEngine(cps, i);
998 FloatToFront(&appData.recentEngineList, engineLine);
999 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1003 while(q = strchr(p, SLASH)) p = q+1;
1004 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1005 if(engineDir[0] != NULLCHAR) {
1006 ASSIGN(appData.directory[i], engineDir); p = engineName;
1007 } else if(p != engineName) { // derive directory from engine path, when not given
1009 ASSIGN(appData.directory[i], engineName);
1011 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1012 } else { ASSIGN(appData.directory[i], "."); }
1013 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1015 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1016 snprintf(command, MSG_SIZ, "%s %s", p, params);
1019 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1020 ASSIGN(appData.chessProgram[i], p);
1021 appData.isUCI[i] = isUCI;
1022 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1023 appData.hasOwnBookUCI[i] = hasBook;
1024 if(!nickName[0]) useNick = FALSE;
1025 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1029 q = firstChessProgramNames;
1030 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1031 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1032 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1033 quote, p, quote, appData.directory[i],
1034 useNick ? " -fn \"" : "",
1035 useNick ? nickName : "",
1036 useNick ? "\"" : "",
1037 v1 ? " -firstProtocolVersion 1" : "",
1038 hasBook ? "" : " -fNoOwnBookUCI",
1039 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1040 storeVariant ? " -variant " : "",
1041 storeVariant ? VariantName(gameInfo.variant) : "");
1042 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1043 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1044 if(insert != q) insert[-1] = NULLCHAR;
1045 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1047 FloatToFront(&appData.recentEngineList, buf);
1049 ReplaceEngine(cps, i);
1055 int matched, min, sec;
1057 * Parse timeControl resource
1059 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1060 appData.movesPerSession)) {
1062 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1063 DisplayFatalError(buf, 0, 2);
1067 * Parse searchTime resource
1069 if (*appData.searchTime != NULLCHAR) {
1070 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1072 searchTime = min * 60;
1073 } else if (matched == 2) {
1074 searchTime = min * 60 + sec;
1077 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1078 DisplayFatalError(buf, 0, 2);
1087 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1088 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1090 GetTimeMark(&programStartTime);
1091 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1092 appData.seedBase = random() + (random()<<15);
1093 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1095 ClearProgramStats();
1096 programStats.ok_to_send = 1;
1097 programStats.seen_stat = 0;
1100 * Initialize game list
1106 * Internet chess server status
1108 if (appData.icsActive) {
1109 appData.matchMode = FALSE;
1110 appData.matchGames = 0;
1112 appData.noChessProgram = !appData.zippyPlay;
1114 appData.zippyPlay = FALSE;
1115 appData.zippyTalk = FALSE;
1116 appData.noChessProgram = TRUE;
1118 if (*appData.icsHelper != NULLCHAR) {
1119 appData.useTelnet = TRUE;
1120 appData.telnetProgram = appData.icsHelper;
1123 appData.zippyTalk = appData.zippyPlay = FALSE;
1126 /* [AS] Initialize pv info list [HGM] and game state */
1130 for( i=0; i<=framePtr; i++ ) {
1131 pvInfoList[i].depth = -1;
1132 boards[i][EP_STATUS] = EP_NONE;
1133 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1139 /* [AS] Adjudication threshold */
1140 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1142 InitEngine(&first, 0);
1143 InitEngine(&second, 1);
1146 pairing.which = "pairing"; // pairing engine
1147 pairing.pr = NoProc;
1149 pairing.program = appData.pairingEngine;
1150 pairing.host = "localhost";
1153 if (appData.icsActive) {
1154 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1155 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1156 appData.clockMode = FALSE;
1157 first.sendTime = second.sendTime = 0;
1161 /* Override some settings from environment variables, for backward
1162 compatibility. Unfortunately it's not feasible to have the env
1163 vars just set defaults, at least in xboard. Ugh.
1165 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1170 if (!appData.icsActive) {
1174 /* Check for variants that are supported only in ICS mode,
1175 or not at all. Some that are accepted here nevertheless
1176 have bugs; see comments below.
1178 VariantClass variant = StringToVariant(appData.variant);
1180 case VariantBughouse: /* need four players and two boards */
1181 case VariantKriegspiel: /* need to hide pieces and move details */
1182 /* case VariantFischeRandom: (Fabien: moved below) */
1183 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1184 if( (len >= MSG_SIZ) && appData.debugMode )
1185 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1187 DisplayFatalError(buf, 0, 2);
1190 case VariantUnknown:
1191 case VariantLoadable:
1201 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1202 if( (len >= MSG_SIZ) && appData.debugMode )
1203 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1205 DisplayFatalError(buf, 0, 2);
1208 case VariantNormal: /* definitely works! */
1209 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1210 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1213 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1214 case VariantFairy: /* [HGM] TestLegality definitely off! */
1215 case VariantGothic: /* [HGM] should work */
1216 case VariantCapablanca: /* [HGM] should work */
1217 case VariantCourier: /* [HGM] initial forced moves not implemented */
1218 case VariantShogi: /* [HGM] could still mate with pawn drop */
1219 case VariantChu: /* [HGM] experimental */
1220 case VariantKnightmate: /* [HGM] should work */
1221 case VariantCylinder: /* [HGM] untested */
1222 case VariantFalcon: /* [HGM] untested */
1223 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1224 offboard interposition not understood */
1225 case VariantWildCastle: /* pieces not automatically shuffled */
1226 case VariantNoCastle: /* pieces not automatically shuffled */
1227 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1228 case VariantLosers: /* should work except for win condition,
1229 and doesn't know captures are mandatory */
1230 case VariantSuicide: /* should work except for win condition,
1231 and doesn't know captures are mandatory */
1232 case VariantGiveaway: /* should work except for win condition,
1233 and doesn't know captures are mandatory */
1234 case VariantTwoKings: /* should work */
1235 case VariantAtomic: /* should work except for win condition */
1236 case Variant3Check: /* should work except for win condition */
1237 case VariantShatranj: /* should work except for all win conditions */
1238 case VariantMakruk: /* should work except for draw countdown */
1239 case VariantASEAN : /* should work except for draw countdown */
1240 case VariantBerolina: /* might work if TestLegality is off */
1241 case VariantCapaRandom: /* should work */
1242 case VariantJanus: /* should work */
1243 case VariantSuper: /* experimental */
1244 case VariantGreat: /* experimental, requires legality testing to be off */
1245 case VariantSChess: /* S-Chess, should work */
1246 case VariantGrand: /* should work */
1247 case VariantSpartan: /* should work */
1248 case VariantLion: /* should work */
1249 case VariantChuChess: /* should work */
1257 NextIntegerFromString (char ** str, long * value)
1262 while( *s == ' ' || *s == '\t' ) {
1268 if( *s >= '0' && *s <= '9' ) {
1269 while( *s >= '0' && *s <= '9' ) {
1270 *value = *value * 10 + (*s - '0');
1283 NextTimeControlFromString (char ** str, long * value)
1286 int result = NextIntegerFromString( str, &temp );
1289 *value = temp * 60; /* Minutes */
1290 if( **str == ':' ) {
1292 result = NextIntegerFromString( str, &temp );
1293 *value += temp; /* Seconds */
1301 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1302 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1303 int result = -1, type = 0; long temp, temp2;
1305 if(**str != ':') return -1; // old params remain in force!
1307 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1308 if( NextIntegerFromString( str, &temp ) ) return -1;
1309 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1312 /* time only: incremental or sudden-death time control */
1313 if(**str == '+') { /* increment follows; read it */
1315 if(**str == '!') type = *(*str)++; // Bronstein TC
1316 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317 *inc = temp2 * 1000;
1318 if(**str == '.') { // read fraction of increment
1319 char *start = ++(*str);
1320 if(result = NextIntegerFromString( str, &temp2)) return -1;
1322 while(start++ < *str) temp2 /= 10;
1326 *moves = 0; *tc = temp * 1000; *incType = type;
1330 (*str)++; /* classical time control */
1331 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1343 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1344 { /* [HGM] get time to add from the multi-session time-control string */
1345 int incType, moves=1; /* kludge to force reading of first session */
1346 long time, increment;
1349 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1351 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1352 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1353 if(movenr == -1) return time; /* last move before new session */
1354 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1355 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1356 if(!moves) return increment; /* current session is incremental */
1357 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1358 } while(movenr >= -1); /* try again for next session */
1360 return 0; // no new time quota on this move
1364 ParseTimeControl (char *tc, float ti, int mps)
1368 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1371 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1372 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1373 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1377 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1379 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1382 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1384 snprintf(buf, MSG_SIZ, ":%s", mytc);
1386 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1388 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1393 /* Parse second time control */
1396 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1404 timeControl_2 = tc2 * 1000;
1414 timeControl = tc1 * 1000;
1417 timeIncrement = ti * 1000; /* convert to ms */
1418 movesPerSession = 0;
1421 movesPerSession = mps;
1429 if (appData.debugMode) {
1430 # ifdef __GIT_VERSION
1431 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1433 fprintf(debugFP, "Version: %s\n", programVersion);
1436 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1438 set_cont_sequence(appData.wrapContSeq);
1439 if (appData.matchGames > 0) {
1440 appData.matchMode = TRUE;
1441 } else if (appData.matchMode) {
1442 appData.matchGames = 1;
1444 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1445 appData.matchGames = appData.sameColorGames;
1446 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1447 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1448 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1451 if (appData.noChessProgram || first.protocolVersion == 1) {
1454 /* kludge: allow timeout for initial "feature" commands */
1456 DisplayMessage("", _("Starting chess program"));
1457 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1462 CalculateIndex (int index, int gameNr)
1463 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1465 if(index > 0) return index; // fixed nmber
1466 if(index == 0) return 1;
1467 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1468 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1473 LoadGameOrPosition (int gameNr)
1474 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1475 if (*appData.loadGameFile != NULLCHAR) {
1476 if (!LoadGameFromFile(appData.loadGameFile,
1477 CalculateIndex(appData.loadGameIndex, gameNr),
1478 appData.loadGameFile, FALSE)) {
1479 DisplayFatalError(_("Bad game file"), 0, 1);
1482 } else if (*appData.loadPositionFile != NULLCHAR) {
1483 if (!LoadPositionFromFile(appData.loadPositionFile,
1484 CalculateIndex(appData.loadPositionIndex, gameNr),
1485 appData.loadPositionFile)) {
1486 DisplayFatalError(_("Bad position file"), 0, 1);
1494 ReserveGame (int gameNr, char resChar)
1496 FILE *tf = fopen(appData.tourneyFile, "r+");
1497 char *p, *q, c, buf[MSG_SIZ];
1498 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1499 safeStrCpy(buf, lastMsg, MSG_SIZ);
1500 DisplayMessage(_("Pick new game"), "");
1501 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1502 ParseArgsFromFile(tf);
1503 p = q = appData.results;
1504 if(appData.debugMode) {
1505 char *r = appData.participants;
1506 fprintf(debugFP, "results = '%s'\n", p);
1507 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1508 fprintf(debugFP, "\n");
1510 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1512 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1513 safeStrCpy(q, p, strlen(p) + 2);
1514 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1515 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1516 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1517 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1520 fseek(tf, -(strlen(p)+4), SEEK_END);
1522 if(c != '"') // depending on DOS or Unix line endings we can be one off
1523 fseek(tf, -(strlen(p)+2), SEEK_END);
1524 else fseek(tf, -(strlen(p)+3), SEEK_END);
1525 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1526 DisplayMessage(buf, "");
1527 free(p); appData.results = q;
1528 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1529 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1530 int round = appData.defaultMatchGames * appData.tourneyType;
1531 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1532 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1533 UnloadEngine(&first); // next game belongs to other pairing;
1534 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1536 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1540 MatchEvent (int mode)
1541 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1543 if(matchMode) { // already in match mode: switch it off
1545 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1548 // if(gameMode != BeginningOfGame) {
1549 // DisplayError(_("You can only start a match from the initial position."), 0);
1553 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1554 /* Set up machine vs. machine match */
1556 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1557 if(appData.tourneyFile[0]) {
1559 if(nextGame > appData.matchGames) {
1561 if(strchr(appData.results, '*') == NULL) {
1563 appData.tourneyCycles++;
1564 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1566 NextTourneyGame(-1, &dummy);
1568 if(nextGame <= appData.matchGames) {
1569 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1571 ScheduleDelayedEvent(NextMatchGame, 10000);
1576 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1577 DisplayError(buf, 0);
1578 appData.tourneyFile[0] = 0;
1582 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1583 DisplayFatalError(_("Can't have a match with no chess programs"),
1588 matchGame = roundNr = 1;
1589 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1593 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1596 InitBackEnd3 P((void))
1598 GameMode initialMode;
1602 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1603 !strcmp(appData.variant, "normal") && // no explicit variant request
1604 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1605 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1606 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1607 char c, *q = first.variants, *p = strchr(q, ',');
1608 if(p) *p = NULLCHAR;
1609 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1611 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1612 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1613 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1614 Reset(TRUE, FALSE); // and re-initialize
1619 InitChessProgram(&first, startedFromSetupPosition);
1621 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1622 free(programVersion);
1623 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1624 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1625 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1628 if (appData.icsActive) {
1630 /* [DM] Make a console window if needed [HGM] merged ifs */
1636 if (*appData.icsCommPort != NULLCHAR)
1637 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1638 appData.icsCommPort);
1640 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1641 appData.icsHost, appData.icsPort);
1643 if( (len >= MSG_SIZ) && appData.debugMode )
1644 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1646 DisplayFatalError(buf, err, 1);
1651 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1653 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1654 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1655 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1656 } else if (appData.noChessProgram) {
1662 if (*appData.cmailGameName != NULLCHAR) {
1664 OpenLoopback(&cmailPR);
1666 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1670 DisplayMessage("", "");
1671 if (StrCaseCmp(appData.initialMode, "") == 0) {
1672 initialMode = BeginningOfGame;
1673 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1674 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1675 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1676 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1679 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1680 initialMode = TwoMachinesPlay;
1681 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1682 initialMode = AnalyzeFile;
1683 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1684 initialMode = AnalyzeMode;
1685 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1686 initialMode = MachinePlaysWhite;
1687 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1688 initialMode = MachinePlaysBlack;
1689 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1690 initialMode = EditGame;
1691 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1692 initialMode = EditPosition;
1693 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1694 initialMode = Training;
1696 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1697 if( (len >= MSG_SIZ) && appData.debugMode )
1698 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1700 DisplayFatalError(buf, 0, 2);
1704 if (appData.matchMode) {
1705 if(appData.tourneyFile[0]) { // start tourney from command line
1707 if(f = fopen(appData.tourneyFile, "r")) {
1708 ParseArgsFromFile(f); // make sure tourney parmeters re known
1710 appData.clockMode = TRUE;
1712 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1715 } else if (*appData.cmailGameName != NULLCHAR) {
1716 /* Set up cmail mode */
1717 ReloadCmailMsgEvent(TRUE);
1719 /* Set up other modes */
1720 if (initialMode == AnalyzeFile) {
1721 if (*appData.loadGameFile == NULLCHAR) {
1722 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1726 if (*appData.loadGameFile != NULLCHAR) {
1727 (void) LoadGameFromFile(appData.loadGameFile,
1728 appData.loadGameIndex,
1729 appData.loadGameFile, TRUE);
1730 } else if (*appData.loadPositionFile != NULLCHAR) {
1731 (void) LoadPositionFromFile(appData.loadPositionFile,
1732 appData.loadPositionIndex,
1733 appData.loadPositionFile);
1734 /* [HGM] try to make self-starting even after FEN load */
1735 /* to allow automatic setup of fairy variants with wtm */
1736 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1737 gameMode = BeginningOfGame;
1738 setboardSpoiledMachineBlack = 1;
1740 /* [HGM] loadPos: make that every new game uses the setup */
1741 /* from file as long as we do not switch variant */
1742 if(!blackPlaysFirst) {
1743 startedFromPositionFile = TRUE;
1744 CopyBoard(filePosition, boards[0]);
1745 CopyBoard(initialPosition, boards[0]);
1747 } else if(*appData.fen != NULLCHAR) {
1748 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1749 startedFromPositionFile = TRUE;
1753 if (initialMode == AnalyzeMode) {
1754 if (appData.noChessProgram) {
1755 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1758 if (appData.icsActive) {
1759 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1763 } else if (initialMode == AnalyzeFile) {
1764 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1765 ShowThinkingEvent();
1767 AnalysisPeriodicEvent(1);
1768 } else if (initialMode == MachinePlaysWhite) {
1769 if (appData.noChessProgram) {
1770 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1774 if (appData.icsActive) {
1775 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1779 MachineWhiteEvent();
1780 } else if (initialMode == MachinePlaysBlack) {
1781 if (appData.noChessProgram) {
1782 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1786 if (appData.icsActive) {
1787 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1791 MachineBlackEvent();
1792 } else if (initialMode == TwoMachinesPlay) {
1793 if (appData.noChessProgram) {
1794 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1798 if (appData.icsActive) {
1799 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1804 } else if (initialMode == EditGame) {
1806 } else if (initialMode == EditPosition) {
1807 EditPositionEvent();
1808 } else if (initialMode == Training) {
1809 if (*appData.loadGameFile == NULLCHAR) {
1810 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1819 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1821 DisplayBook(current+1);
1823 MoveHistorySet( movelist, first, last, current, pvInfoList );
1825 EvalGraphSet( first, last, current, pvInfoList );
1827 MakeEngineOutputTitle();
1831 * Establish will establish a contact to a remote host.port.
1832 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1833 * used to talk to the host.
1834 * Returns 0 if okay, error code if not.
1841 if (*appData.icsCommPort != NULLCHAR) {
1842 /* Talk to the host through a serial comm port */
1843 return OpenCommPort(appData.icsCommPort, &icsPR);
1845 } else if (*appData.gateway != NULLCHAR) {
1846 if (*appData.remoteShell == NULLCHAR) {
1847 /* Use the rcmd protocol to run telnet program on a gateway host */
1848 snprintf(buf, sizeof(buf), "%s %s %s",
1849 appData.telnetProgram, appData.icsHost, appData.icsPort);
1850 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1853 /* Use the rsh program to run telnet program on a gateway host */
1854 if (*appData.remoteUser == NULLCHAR) {
1855 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1856 appData.gateway, appData.telnetProgram,
1857 appData.icsHost, appData.icsPort);
1859 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1860 appData.remoteShell, appData.gateway,
1861 appData.remoteUser, appData.telnetProgram,
1862 appData.icsHost, appData.icsPort);
1864 return StartChildProcess(buf, "", &icsPR);
1867 } else if (appData.useTelnet) {
1868 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1871 /* TCP socket interface differs somewhat between
1872 Unix and NT; handle details in the front end.
1874 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1879 EscapeExpand (char *p, char *q)
1880 { // [HGM] initstring: routine to shape up string arguments
1881 while(*p++ = *q++) if(p[-1] == '\\')
1883 case 'n': p[-1] = '\n'; break;
1884 case 'r': p[-1] = '\r'; break;
1885 case 't': p[-1] = '\t'; break;
1886 case '\\': p[-1] = '\\'; break;
1887 case 0: *p = 0; return;
1888 default: p[-1] = q[-1]; break;
1893 show_bytes (FILE *fp, char *buf, int count)
1896 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1897 fprintf(fp, "\\%03o", *buf & 0xff);
1906 /* Returns an errno value */
1908 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1910 char buf[8192], *p, *q, *buflim;
1911 int left, newcount, outcount;
1913 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1914 *appData.gateway != NULLCHAR) {
1915 if (appData.debugMode) {
1916 fprintf(debugFP, ">ICS: ");
1917 show_bytes(debugFP, message, count);
1918 fprintf(debugFP, "\n");
1920 return OutputToProcess(pr, message, count, outError);
1923 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1930 if (appData.debugMode) {
1931 fprintf(debugFP, ">ICS: ");
1932 show_bytes(debugFP, buf, newcount);
1933 fprintf(debugFP, "\n");
1935 outcount = OutputToProcess(pr, buf, newcount, outError);
1936 if (outcount < newcount) return -1; /* to be sure */
1943 } else if (((unsigned char) *p) == TN_IAC) {
1944 *q++ = (char) TN_IAC;
1951 if (appData.debugMode) {
1952 fprintf(debugFP, ">ICS: ");
1953 show_bytes(debugFP, buf, newcount);
1954 fprintf(debugFP, "\n");
1956 outcount = OutputToProcess(pr, buf, newcount, outError);
1957 if (outcount < newcount) return -1; /* to be sure */
1962 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1964 int outError, outCount;
1965 static int gotEof = 0;
1968 /* Pass data read from player on to ICS */
1971 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1972 if (outCount < count) {
1973 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1975 if(have_sent_ICS_logon == 2) {
1976 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1977 fprintf(ini, "%s", message);
1978 have_sent_ICS_logon = 3;
1980 have_sent_ICS_logon = 1;
1981 } else if(have_sent_ICS_logon == 3) {
1982 fprintf(ini, "%s", message);
1984 have_sent_ICS_logon = 1;
1986 } else if (count < 0) {
1987 RemoveInputSource(isr);
1988 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1989 } else if (gotEof++ > 0) {
1990 RemoveInputSource(isr);
1991 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1997 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1998 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1999 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2000 SendToICS("date\n");
2001 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2004 /* added routine for printf style output to ics */
2006 ics_printf (char *format, ...)
2008 char buffer[MSG_SIZ];
2011 va_start(args, format);
2012 vsnprintf(buffer, sizeof(buffer), format, args);
2013 buffer[sizeof(buffer)-1] = '\0';
2021 int count, outCount, outError;
2023 if (icsPR == NoProc) return;
2026 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2027 if (outCount < count) {
2028 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2032 /* This is used for sending logon scripts to the ICS. Sending
2033 without a delay causes problems when using timestamp on ICC
2034 (at least on my machine). */
2036 SendToICSDelayed (char *s, long msdelay)
2038 int count, outCount, outError;
2040 if (icsPR == NoProc) return;
2043 if (appData.debugMode) {
2044 fprintf(debugFP, ">ICS: ");
2045 show_bytes(debugFP, s, count);
2046 fprintf(debugFP, "\n");
2048 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2050 if (outCount < count) {
2051 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2056 /* Remove all highlighting escape sequences in s
2057 Also deletes any suffix starting with '('
2060 StripHighlightAndTitle (char *s)
2062 static char retbuf[MSG_SIZ];
2065 while (*s != NULLCHAR) {
2066 while (*s == '\033') {
2067 while (*s != NULLCHAR && !isalpha(*s)) s++;
2068 if (*s != NULLCHAR) s++;
2070 while (*s != NULLCHAR && *s != '\033') {
2071 if (*s == '(' || *s == '[') {
2082 /* Remove all highlighting escape sequences in s */
2084 StripHighlight (char *s)
2086 static char retbuf[MSG_SIZ];
2089 while (*s != NULLCHAR) {
2090 while (*s == '\033') {
2091 while (*s != NULLCHAR && !isalpha(*s)) s++;
2092 if (*s != NULLCHAR) s++;
2094 while (*s != NULLCHAR && *s != '\033') {
2102 char engineVariant[MSG_SIZ];
2103 char *variantNames[] = VARIANT_NAMES;
2105 VariantName (VariantClass v)
2107 if(v == VariantUnknown || *engineVariant) return engineVariant;
2108 return variantNames[v];
2112 /* Identify a variant from the strings the chess servers use or the
2113 PGN Variant tag names we use. */
2115 StringToVariant (char *e)
2119 VariantClass v = VariantNormal;
2120 int i, found = FALSE;
2121 char buf[MSG_SIZ], c;
2126 /* [HGM] skip over optional board-size prefixes */
2127 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2128 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2129 while( *e++ != '_');
2132 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2136 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2137 if (p = StrCaseStr(e, variantNames[i])) {
2138 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2139 v = (VariantClass) i;
2146 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2147 || StrCaseStr(e, "wild/fr")
2148 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2149 v = VariantFischeRandom;
2150 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2151 (i = 1, p = StrCaseStr(e, "w"))) {
2153 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2160 case 0: /* FICS only, actually */
2162 /* Castling legal even if K starts on d-file */
2163 v = VariantWildCastle;
2168 /* Castling illegal even if K & R happen to start in
2169 normal positions. */
2170 v = VariantNoCastle;
2183 /* Castling legal iff K & R start in normal positions */
2189 /* Special wilds for position setup; unclear what to do here */
2190 v = VariantLoadable;
2193 /* Bizarre ICC game */
2194 v = VariantTwoKings;
2197 v = VariantKriegspiel;
2203 v = VariantFischeRandom;
2206 v = VariantCrazyhouse;
2209 v = VariantBughouse;
2215 /* Not quite the same as FICS suicide! */
2216 v = VariantGiveaway;
2222 v = VariantShatranj;
2225 /* Temporary names for future ICC types. The name *will* change in
2226 the next xboard/WinBoard release after ICC defines it. */
2264 v = VariantCapablanca;
2267 v = VariantKnightmate;
2273 v = VariantCylinder;
2279 v = VariantCapaRandom;
2282 v = VariantBerolina;
2294 /* Found "wild" or "w" in the string but no number;
2295 must assume it's normal chess. */
2299 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2300 if( (len >= MSG_SIZ) && appData.debugMode )
2301 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2303 DisplayError(buf, 0);
2309 if (appData.debugMode) {
2310 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2311 e, wnum, VariantName(v));
2316 static int leftover_start = 0, leftover_len = 0;
2317 char star_match[STAR_MATCH_N][MSG_SIZ];
2319 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2320 advance *index beyond it, and set leftover_start to the new value of
2321 *index; else return FALSE. If pattern contains the character '*', it
2322 matches any sequence of characters not containing '\r', '\n', or the
2323 character following the '*' (if any), and the matched sequence(s) are
2324 copied into star_match.
2327 looking_at ( char *buf, int *index, char *pattern)
2329 char *bufp = &buf[*index], *patternp = pattern;
2331 char *matchp = star_match[0];
2334 if (*patternp == NULLCHAR) {
2335 *index = leftover_start = bufp - buf;
2339 if (*bufp == NULLCHAR) return FALSE;
2340 if (*patternp == '*') {
2341 if (*bufp == *(patternp + 1)) {
2343 matchp = star_match[++star_count];
2347 } else if (*bufp == '\n' || *bufp == '\r') {
2349 if (*patternp == NULLCHAR)
2354 *matchp++ = *bufp++;
2358 if (*patternp != *bufp) return FALSE;
2365 SendToPlayer (char *data, int length)
2367 int error, outCount;
2368 outCount = OutputToProcess(NoProc, data, length, &error);
2369 if (outCount < length) {
2370 DisplayFatalError(_("Error writing to display"), error, 1);
2375 PackHolding (char packed[], char *holding)
2385 switch (runlength) {
2396 sprintf(q, "%d", runlength);
2408 /* Telnet protocol requests from the front end */
2410 TelnetRequest (unsigned char ddww, unsigned char option)
2412 unsigned char msg[3];
2413 int outCount, outError;
2415 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2417 if (appData.debugMode) {
2418 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2434 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2443 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2446 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2451 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2453 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2460 if (!appData.icsActive) return;
2461 TelnetRequest(TN_DO, TN_ECHO);
2467 if (!appData.icsActive) return;
2468 TelnetRequest(TN_DONT, TN_ECHO);
2472 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2474 /* put the holdings sent to us by the server on the board holdings area */
2475 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2479 if(gameInfo.holdingsWidth < 2) return;
2480 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2481 return; // prevent overwriting by pre-board holdings
2483 if( (int)lowestPiece >= BlackPawn ) {
2486 holdingsStartRow = BOARD_HEIGHT-1;
2489 holdingsColumn = BOARD_WIDTH-1;
2490 countsColumn = BOARD_WIDTH-2;
2491 holdingsStartRow = 0;
2495 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2496 board[i][holdingsColumn] = EmptySquare;
2497 board[i][countsColumn] = (ChessSquare) 0;
2499 while( (p=*holdings++) != NULLCHAR ) {
2500 piece = CharToPiece( ToUpper(p) );
2501 if(piece == EmptySquare) continue;
2502 /*j = (int) piece - (int) WhitePawn;*/
2503 j = PieceToNumber(piece);
2504 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2505 if(j < 0) continue; /* should not happen */
2506 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2507 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2508 board[holdingsStartRow+j*direction][countsColumn]++;
2514 VariantSwitch (Board board, VariantClass newVariant)
2516 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2517 static Board oldBoard;
2519 startedFromPositionFile = FALSE;
2520 if(gameInfo.variant == newVariant) return;
2522 /* [HGM] This routine is called each time an assignment is made to
2523 * gameInfo.variant during a game, to make sure the board sizes
2524 * are set to match the new variant. If that means adding or deleting
2525 * holdings, we shift the playing board accordingly
2526 * This kludge is needed because in ICS observe mode, we get boards
2527 * of an ongoing game without knowing the variant, and learn about the
2528 * latter only later. This can be because of the move list we requested,
2529 * in which case the game history is refilled from the beginning anyway,
2530 * but also when receiving holdings of a crazyhouse game. In the latter
2531 * case we want to add those holdings to the already received position.
2535 if (appData.debugMode) {
2536 fprintf(debugFP, "Switch board from %s to %s\n",
2537 VariantName(gameInfo.variant), VariantName(newVariant));
2538 setbuf(debugFP, NULL);
2540 shuffleOpenings = 0; /* [HGM] shuffle */
2541 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2545 newWidth = 9; newHeight = 9;
2546 gameInfo.holdingsSize = 7;
2547 case VariantBughouse:
2548 case VariantCrazyhouse:
2549 newHoldingsWidth = 2; break;
2553 newHoldingsWidth = 2;
2554 gameInfo.holdingsSize = 8;
2557 case VariantCapablanca:
2558 case VariantCapaRandom:
2561 newHoldingsWidth = gameInfo.holdingsSize = 0;
2564 if(newWidth != gameInfo.boardWidth ||
2565 newHeight != gameInfo.boardHeight ||
2566 newHoldingsWidth != gameInfo.holdingsWidth ) {
2568 /* shift position to new playing area, if needed */
2569 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2570 for(i=0; i<BOARD_HEIGHT; i++)
2571 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2572 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2574 for(i=0; i<newHeight; i++) {
2575 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2576 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2578 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2579 for(i=0; i<BOARD_HEIGHT; i++)
2580 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2581 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2584 board[HOLDINGS_SET] = 0;
2585 gameInfo.boardWidth = newWidth;
2586 gameInfo.boardHeight = newHeight;
2587 gameInfo.holdingsWidth = newHoldingsWidth;
2588 gameInfo.variant = newVariant;
2589 InitDrawingSizes(-2, 0);
2590 } else gameInfo.variant = newVariant;
2591 CopyBoard(oldBoard, board); // remember correctly formatted board
2592 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2593 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2596 static int loggedOn = FALSE;
2598 /*-- Game start info cache: --*/
2600 char gs_kind[MSG_SIZ];
2601 static char player1Name[128] = "";
2602 static char player2Name[128] = "";
2603 static char cont_seq[] = "\n\\ ";
2604 static int player1Rating = -1;
2605 static int player2Rating = -1;
2606 /*----------------------------*/
2608 ColorClass curColor = ColorNormal;
2609 int suppressKibitz = 0;
2612 Boolean soughtPending = FALSE;
2613 Boolean seekGraphUp;
2614 #define MAX_SEEK_ADS 200
2616 char *seekAdList[MAX_SEEK_ADS];
2617 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2618 float tcList[MAX_SEEK_ADS];
2619 char colorList[MAX_SEEK_ADS];
2620 int nrOfSeekAds = 0;
2621 int minRating = 1010, maxRating = 2800;
2622 int hMargin = 10, vMargin = 20, h, w;
2623 extern int squareSize, lineGap;
2628 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2629 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2630 if(r < minRating+100 && r >=0 ) r = minRating+100;
2631 if(r > maxRating) r = maxRating;
2632 if(tc < 1.f) tc = 1.f;
2633 if(tc > 95.f) tc = 95.f;
2634 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2635 y = ((double)r - minRating)/(maxRating - minRating)
2636 * (h-vMargin-squareSize/8-1) + vMargin;
2637 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2638 if(strstr(seekAdList[i], " u ")) color = 1;
2639 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2640 !strstr(seekAdList[i], "bullet") &&
2641 !strstr(seekAdList[i], "blitz") &&
2642 !strstr(seekAdList[i], "standard") ) color = 2;
2643 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2644 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2648 PlotSingleSeekAd (int i)
2654 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2656 char buf[MSG_SIZ], *ext = "";
2657 VariantClass v = StringToVariant(type);
2658 if(strstr(type, "wild")) {
2659 ext = type + 4; // append wild number
2660 if(v == VariantFischeRandom) type = "chess960"; else
2661 if(v == VariantLoadable) type = "setup"; else
2662 type = VariantName(v);
2664 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2665 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2666 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2667 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2668 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2669 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2670 seekNrList[nrOfSeekAds] = nr;
2671 zList[nrOfSeekAds] = 0;
2672 seekAdList[nrOfSeekAds++] = StrSave(buf);
2673 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2678 EraseSeekDot (int i)
2680 int x = xList[i], y = yList[i], d=squareSize/4, k;
2681 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2682 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2683 // now replot every dot that overlapped
2684 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2685 int xx = xList[k], yy = yList[k];
2686 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2687 DrawSeekDot(xx, yy, colorList[k]);
2692 RemoveSeekAd (int nr)
2695 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2697 if(seekAdList[i]) free(seekAdList[i]);
2698 seekAdList[i] = seekAdList[--nrOfSeekAds];
2699 seekNrList[i] = seekNrList[nrOfSeekAds];
2700 ratingList[i] = ratingList[nrOfSeekAds];
2701 colorList[i] = colorList[nrOfSeekAds];
2702 tcList[i] = tcList[nrOfSeekAds];
2703 xList[i] = xList[nrOfSeekAds];
2704 yList[i] = yList[nrOfSeekAds];
2705 zList[i] = zList[nrOfSeekAds];
2706 seekAdList[nrOfSeekAds] = NULL;
2712 MatchSoughtLine (char *line)
2714 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2715 int nr, base, inc, u=0; char dummy;
2717 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2718 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2720 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2721 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2722 // match: compact and save the line
2723 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2733 if(!seekGraphUp) return FALSE;
2734 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2735 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2737 DrawSeekBackground(0, 0, w, h);
2738 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2739 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2740 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2741 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2743 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2746 snprintf(buf, MSG_SIZ, "%d", i);
2747 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2750 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2751 for(i=1; i<100; i+=(i<10?1:5)) {
2752 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2753 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2754 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2756 snprintf(buf, MSG_SIZ, "%d", i);
2757 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2760 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2765 SeekGraphClick (ClickType click, int x, int y, int moving)
2767 static int lastDown = 0, displayed = 0, lastSecond;
2768 if(y < 0) return FALSE;
2769 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2770 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2771 if(!seekGraphUp) return FALSE;
2772 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2773 DrawPosition(TRUE, NULL);
2776 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2777 if(click == Release || moving) return FALSE;
2779 soughtPending = TRUE;
2780 SendToICS(ics_prefix);
2781 SendToICS("sought\n"); // should this be "sought all"?
2782 } else { // issue challenge based on clicked ad
2783 int dist = 10000; int i, closest = 0, second = 0;
2784 for(i=0; i<nrOfSeekAds; i++) {
2785 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2786 if(d < dist) { dist = d; closest = i; }
2787 second += (d - zList[i] < 120); // count in-range ads
2788 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2792 second = (second > 1);
2793 if(displayed != closest || second != lastSecond) {
2794 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2795 lastSecond = second; displayed = closest;
2797 if(click == Press) {
2798 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2801 } // on press 'hit', only show info
2802 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2803 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2804 SendToICS(ics_prefix);
2806 return TRUE; // let incoming board of started game pop down the graph
2807 } else if(click == Release) { // release 'miss' is ignored
2808 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2809 if(moving == 2) { // right up-click
2810 nrOfSeekAds = 0; // refresh graph
2811 soughtPending = TRUE;
2812 SendToICS(ics_prefix);
2813 SendToICS("sought\n"); // should this be "sought all"?
2816 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2817 // press miss or release hit 'pop down' seek graph
2818 seekGraphUp = FALSE;
2819 DrawPosition(TRUE, NULL);
2825 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2827 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2828 #define STARTED_NONE 0
2829 #define STARTED_MOVES 1
2830 #define STARTED_BOARD 2
2831 #define STARTED_OBSERVE 3
2832 #define STARTED_HOLDINGS 4
2833 #define STARTED_CHATTER 5
2834 #define STARTED_COMMENT 6
2835 #define STARTED_MOVES_NOHIDE 7
2837 static int started = STARTED_NONE;
2838 static char parse[20000];
2839 static int parse_pos = 0;
2840 static char buf[BUF_SIZE + 1];
2841 static int firstTime = TRUE, intfSet = FALSE;
2842 static ColorClass prevColor = ColorNormal;
2843 static int savingComment = FALSE;
2844 static int cmatch = 0; // continuation sequence match
2851 int backup; /* [DM] For zippy color lines */
2853 char talker[MSG_SIZ]; // [HGM] chat
2854 int channel, collective=0;
2856 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2858 if (appData.debugMode) {
2860 fprintf(debugFP, "<ICS: ");
2861 show_bytes(debugFP, data, count);
2862 fprintf(debugFP, "\n");
2866 if (appData.debugMode) { int f = forwardMostMove;
2867 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2868 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2869 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2872 /* If last read ended with a partial line that we couldn't parse,
2873 prepend it to the new read and try again. */
2874 if (leftover_len > 0) {
2875 for (i=0; i<leftover_len; i++)
2876 buf[i] = buf[leftover_start + i];
2879 /* copy new characters into the buffer */
2880 bp = buf + leftover_len;
2881 buf_len=leftover_len;
2882 for (i=0; i<count; i++)
2885 if (data[i] == '\r')
2888 // join lines split by ICS?
2889 if (!appData.noJoin)
2892 Joining just consists of finding matches against the
2893 continuation sequence, and discarding that sequence
2894 if found instead of copying it. So, until a match
2895 fails, there's nothing to do since it might be the
2896 complete sequence, and thus, something we don't want
2899 if (data[i] == cont_seq[cmatch])
2902 if (cmatch == strlen(cont_seq))
2904 cmatch = 0; // complete match. just reset the counter
2907 it's possible for the ICS to not include the space
2908 at the end of the last word, making our [correct]
2909 join operation fuse two separate words. the server
2910 does this when the space occurs at the width setting.
2912 if (!buf_len || buf[buf_len-1] != ' ')
2923 match failed, so we have to copy what matched before
2924 falling through and copying this character. In reality,
2925 this will only ever be just the newline character, but
2926 it doesn't hurt to be precise.
2928 strncpy(bp, cont_seq, cmatch);
2940 buf[buf_len] = NULLCHAR;
2941 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2946 while (i < buf_len) {
2947 /* Deal with part of the TELNET option negotiation
2948 protocol. We refuse to do anything beyond the
2949 defaults, except that we allow the WILL ECHO option,
2950 which ICS uses to turn off password echoing when we are
2951 directly connected to it. We reject this option
2952 if localLineEditing mode is on (always on in xboard)
2953 and we are talking to port 23, which might be a real
2954 telnet server that will try to keep WILL ECHO on permanently.
2956 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2957 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2958 unsigned char option;
2960 switch ((unsigned char) buf[++i]) {
2962 if (appData.debugMode)
2963 fprintf(debugFP, "\n<WILL ");
2964 switch (option = (unsigned char) buf[++i]) {
2966 if (appData.debugMode)
2967 fprintf(debugFP, "ECHO ");
2968 /* Reply only if this is a change, according
2969 to the protocol rules. */
2970 if (remoteEchoOption) break;
2971 if (appData.localLineEditing &&
2972 atoi(appData.icsPort) == TN_PORT) {
2973 TelnetRequest(TN_DONT, TN_ECHO);
2976 TelnetRequest(TN_DO, TN_ECHO);
2977 remoteEchoOption = TRUE;
2981 if (appData.debugMode)
2982 fprintf(debugFP, "%d ", option);
2983 /* Whatever this is, we don't want it. */
2984 TelnetRequest(TN_DONT, option);
2989 if (appData.debugMode)
2990 fprintf(debugFP, "\n<WONT ");
2991 switch (option = (unsigned char) buf[++i]) {
2993 if (appData.debugMode)
2994 fprintf(debugFP, "ECHO ");
2995 /* Reply only if this is a change, according
2996 to the protocol rules. */
2997 if (!remoteEchoOption) break;
2999 TelnetRequest(TN_DONT, TN_ECHO);
3000 remoteEchoOption = FALSE;
3003 if (appData.debugMode)
3004 fprintf(debugFP, "%d ", (unsigned char) option);
3005 /* Whatever this is, it must already be turned
3006 off, because we never agree to turn on
3007 anything non-default, so according to the
3008 protocol rules, we don't reply. */
3013 if (appData.debugMode)
3014 fprintf(debugFP, "\n<DO ");
3015 switch (option = (unsigned char) buf[++i]) {
3017 /* Whatever this is, we refuse to do it. */
3018 if (appData.debugMode)
3019 fprintf(debugFP, "%d ", option);
3020 TelnetRequest(TN_WONT, option);
3025 if (appData.debugMode)
3026 fprintf(debugFP, "\n<DONT ");
3027 switch (option = (unsigned char) buf[++i]) {
3029 if (appData.debugMode)
3030 fprintf(debugFP, "%d ", option);
3031 /* Whatever this is, we are already not doing
3032 it, because we never agree to do anything
3033 non-default, so according to the protocol
3034 rules, we don't reply. */
3039 if (appData.debugMode)
3040 fprintf(debugFP, "\n<IAC ");
3041 /* Doubled IAC; pass it through */
3045 if (appData.debugMode)
3046 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3047 /* Drop all other telnet commands on the floor */
3050 if (oldi > next_out)
3051 SendToPlayer(&buf[next_out], oldi - next_out);
3057 /* OK, this at least will *usually* work */
3058 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3062 if (loggedOn && !intfSet) {
3063 if (ics_type == ICS_ICC) {
3064 snprintf(str, MSG_SIZ,
3065 "/set-quietly interface %s\n/set-quietly style 12\n",
3067 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068 strcat(str, "/set-2 51 1\n/set seek 1\n");
3069 } else if (ics_type == ICS_CHESSNET) {
3070 snprintf(str, MSG_SIZ, "/style 12\n");
3072 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3073 strcat(str, programVersion);
3074 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3075 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3076 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3078 strcat(str, "$iset nohighlight 1\n");
3080 strcat(str, "$iset lock 1\n$style 12\n");
3083 NotifyFrontendLogin();
3087 if (started == STARTED_COMMENT) {
3088 /* Accumulate characters in comment */
3089 parse[parse_pos++] = buf[i];
3090 if (buf[i] == '\n') {
3091 parse[parse_pos] = NULLCHAR;
3092 if(chattingPartner>=0) {
3094 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3095 OutputChatMessage(chattingPartner, mess);
3096 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3098 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3099 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3100 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3101 OutputChatMessage(p, mess);
3105 chattingPartner = -1;
3106 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3109 if(!suppressKibitz) // [HGM] kibitz
3110 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3111 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3112 int nrDigit = 0, nrAlph = 0, j;
3113 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3114 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3115 parse[parse_pos] = NULLCHAR;
3116 // try to be smart: if it does not look like search info, it should go to
3117 // ICS interaction window after all, not to engine-output window.
3118 for(j=0; j<parse_pos; j++) { // count letters and digits
3119 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3120 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3121 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3123 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3124 int depth=0; float score;
3125 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3126 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3127 pvInfoList[forwardMostMove-1].depth = depth;
3128 pvInfoList[forwardMostMove-1].score = 100*score;
3130 OutputKibitz(suppressKibitz, parse);
3133 if(gameMode == IcsObserving) // restore original ICS messages
3134 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3135 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3137 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3138 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3139 SendToPlayer(tmp, strlen(tmp));
3141 next_out = i+1; // [HGM] suppress printing in ICS window
3143 started = STARTED_NONE;
3145 /* Don't match patterns against characters in comment */
3150 if (started == STARTED_CHATTER) {
3151 if (buf[i] != '\n') {
3152 /* Don't match patterns against characters in chatter */
3156 started = STARTED_NONE;
3157 if(suppressKibitz) next_out = i+1;
3160 /* Kludge to deal with rcmd protocol */
3161 if (firstTime && looking_at(buf, &i, "\001*")) {
3162 DisplayFatalError(&buf[1], 0, 1);
3168 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3171 if (appData.debugMode)
3172 fprintf(debugFP, "ics_type %d\n", ics_type);
3175 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3176 ics_type = ICS_FICS;
3178 if (appData.debugMode)
3179 fprintf(debugFP, "ics_type %d\n", ics_type);
3182 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3183 ics_type = ICS_CHESSNET;
3185 if (appData.debugMode)
3186 fprintf(debugFP, "ics_type %d\n", ics_type);
3191 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3192 looking_at(buf, &i, "Logging you in as \"*\"") ||
3193 looking_at(buf, &i, "will be \"*\""))) {
3194 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3198 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3200 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3201 DisplayIcsInteractionTitle(buf);
3202 have_set_title = TRUE;
3205 /* skip finger notes */
3206 if (started == STARTED_NONE &&
3207 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3208 (buf[i] == '1' && buf[i+1] == '0')) &&
3209 buf[i+2] == ':' && buf[i+3] == ' ') {
3210 started = STARTED_CHATTER;
3216 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3217 if(appData.seekGraph) {
3218 if(soughtPending && MatchSoughtLine(buf+i)) {
3219 i = strstr(buf+i, "rated") - buf;
3220 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221 next_out = leftover_start = i;
3222 started = STARTED_CHATTER;
3223 suppressKibitz = TRUE;
3226 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3227 && looking_at(buf, &i, "* ads displayed")) {
3228 soughtPending = FALSE;
3233 if(appData.autoRefresh) {
3234 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3235 int s = (ics_type == ICS_ICC); // ICC format differs
3237 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3238 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3239 looking_at(buf, &i, "*% "); // eat prompt
3240 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3241 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242 next_out = i; // suppress
3245 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3246 char *p = star_match[0];
3248 if(seekGraphUp) RemoveSeekAd(atoi(p));
3249 while(*p && *p++ != ' '); // next
3251 looking_at(buf, &i, "*% "); // eat prompt
3252 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3259 /* skip formula vars */
3260 if (started == STARTED_NONE &&
3261 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3262 started = STARTED_CHATTER;
3267 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3268 if (appData.autoKibitz && started == STARTED_NONE &&
3269 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3270 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3271 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3272 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3273 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3274 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3275 suppressKibitz = TRUE;
3276 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3279 && (gameMode == IcsPlayingWhite)) ||
3280 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3281 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3282 started = STARTED_CHATTER; // own kibitz we simply discard
3284 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3285 parse_pos = 0; parse[0] = NULLCHAR;
3286 savingComment = TRUE;
3287 suppressKibitz = gameMode != IcsObserving ? 2 :
3288 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3292 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3293 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3294 && atoi(star_match[0])) {
3295 // suppress the acknowledgements of our own autoKibitz
3297 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3298 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3299 SendToPlayer(star_match[0], strlen(star_match[0]));
3300 if(looking_at(buf, &i, "*% ")) // eat prompt
3301 suppressKibitz = FALSE;
3305 } // [HGM] kibitz: end of patch
3307 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3309 // [HGM] chat: intercept tells by users for which we have an open chat window
3311 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3312 looking_at(buf, &i, "* whispers:") ||
3313 looking_at(buf, &i, "* kibitzes:") ||
3314 looking_at(buf, &i, "* shouts:") ||
3315 looking_at(buf, &i, "* c-shouts:") ||
3316 looking_at(buf, &i, "--> * ") ||
3317 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3318 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3319 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3320 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3322 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3323 chattingPartner = -1; collective = 0;
3325 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3326 for(p=0; p<MAX_CHAT; p++) {
3328 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3329 talker[0] = '['; strcat(talker, "] ");
3330 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3331 chattingPartner = p; break;
3334 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3335 for(p=0; p<MAX_CHAT; p++) {
3337 if(!strcmp("kibitzes", chatPartner[p])) {
3338 talker[0] = '['; strcat(talker, "] ");
3339 chattingPartner = p; break;
3342 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3343 for(p=0; p<MAX_CHAT; p++) {
3345 if(!strcmp("whispers", chatPartner[p])) {
3346 talker[0] = '['; strcat(talker, "] ");
3347 chattingPartner = p; break;
3350 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3351 if(buf[i-8] == '-' && buf[i-3] == 't')
3352 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3354 if(!strcmp("c-shouts", chatPartner[p])) {
3355 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3356 chattingPartner = p; break;
3359 if(chattingPartner < 0)
3360 for(p=0; p<MAX_CHAT; p++) {
3362 if(!strcmp("shouts", chatPartner[p])) {
3363 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3364 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3365 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3366 chattingPartner = p; break;
3370 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3371 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3373 Colorize(ColorTell, FALSE);
3374 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3376 chattingPartner = p; break;
3378 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3379 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3380 started = STARTED_COMMENT;
3381 parse_pos = 0; parse[0] = NULLCHAR;
3382 savingComment = 3 + chattingPartner; // counts as TRUE
3383 if(collective == 3) i = oldi; else {
3384 suppressKibitz = TRUE;
3385 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3386 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3390 } // [HGM] chat: end of patch
3393 if (appData.zippyTalk || appData.zippyPlay) {
3394 /* [DM] Backup address for color zippy lines */
3396 if (loggedOn == TRUE)
3397 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3398 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3400 } // [DM] 'else { ' deleted
3402 /* Regular tells and says */
3403 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3404 looking_at(buf, &i, "* (your partner) tells you: ") ||
3405 looking_at(buf, &i, "* says: ") ||
3406 /* Don't color "message" or "messages" output */
3407 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3408 looking_at(buf, &i, "*. * at *:*: ") ||
3409 looking_at(buf, &i, "--* (*:*): ") ||
3410 /* Message notifications (same color as tells) */
3411 looking_at(buf, &i, "* has left a message ") ||
3412 looking_at(buf, &i, "* just sent you a message:\n") ||
3413 /* Whispers and kibitzes */
3414 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3415 looking_at(buf, &i, "* kibitzes: ") ||
3417 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3419 if (tkind == 1 && strchr(star_match[0], ':')) {
3420 /* Avoid "tells you:" spoofs in channels */
3423 if (star_match[0][0] == NULLCHAR ||
3424 strchr(star_match[0], ' ') ||
3425 (tkind == 3 && strchr(star_match[1], ' '))) {
3426 /* Reject bogus matches */
3429 if (appData.colorize) {