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 */
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
273 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border; /* [HGM] width of board rim, needed to size seek graph */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
303 /* States for ics_getting_history */
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
311 /* whosays values for GameEnds */
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
323 /* Different types of move when calling RegisterMove */
325 #define CMAIL_RESIGN 1
327 #define CMAIL_ACCEPT 3
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
334 /* Telnet protocol constants */
345 safeStrCpy (char *dst, const char *src, size_t count)
348 assert( dst != NULL );
349 assert( src != NULL );
352 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353 if( i == count && dst[count-1] != NULLCHAR)
355 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356 if(appData.debugMode)
357 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
363 /* Some compiler can't cast u64 to double
364 * This function do the job for us:
366 * We use the highest bit for cast, this only
367 * works if the highest bit is not
368 * in use (This should not happen)
370 * We used this for all compiler
373 u64ToDouble (u64 value)
376 u64 tmp = value & u64Const(0x7fffffffffffffff);
377 r = (double)(s64)tmp;
378 if (value & u64Const(0x8000000000000000))
379 r += 9.2233720368547758080e18; /* 2^63 */
383 /* Fake up flags for now, as we aren't keeping track of castling
384 availability yet. [HGM] Change of logic: the flag now only
385 indicates the type of castlings allowed by the rule of the game.
386 The actual rights themselves are maintained in the array
387 castlingRights, as part of the game history, and are not probed
393 int flags = F_ALL_CASTLE_OK;
394 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395 switch (gameInfo.variant) {
397 flags &= ~F_ALL_CASTLE_OK;
398 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399 flags |= F_IGNORE_CHECK;
401 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
404 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
406 case VariantKriegspiel:
407 flags |= F_KRIEGSPIEL_CAPTURE;
409 case VariantCapaRandom:
410 case VariantFischeRandom:
411 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412 case VariantNoCastle:
413 case VariantShatranj:
418 flags &= ~F_ALL_CASTLE_OK;
421 case VariantChuChess:
423 flags |= F_NULL_MOVE;
428 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
436 [AS] Note: sometimes, the sscanf() function is used to parse the input
437 into a fixed-size buffer. Because of this, we must be prepared to
438 receive strings as long as the size of the input buffer, which is currently
439 set to 4K for Windows and 8K for the rest.
440 So, we must either allocate sufficiently large buffers here, or
441 reduce the size of the input buffer in the input reading part.
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
449 ChessProgramState first, second, pairing;
451 /* premove variables */
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
492 int have_sent_ICS_logon = 0;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
506 /* animateTraining preserves the state of appData.animate
507 * when Training mode is activated. This allows the
508 * response to be animated when appData.animate == TRUE and
509 * appData.animateDragging == TRUE.
511 Boolean animateTraining;
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int initialRulePlies, FENrulePlies;
523 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
544 ChessSquare FIDEArray[2][BOARD_FILES] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548 BlackKing, BlackBishop, BlackKnight, BlackRook }
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555 BlackKing, BlackKing, BlackKnight, BlackRook }
558 ChessSquare KnightmateArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561 { BlackRook, BlackMan, BlackBishop, BlackQueen,
562 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569 BlackTower, BlackKing, BlackAngel, BlackAlfil }
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackMan, BlackFerz,
590 BlackKing, BlackMan, BlackKnight, BlackRook }
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596 { BlackRook, BlackKnight, BlackMan, BlackFerz,
597 BlackKing, BlackMan, BlackKnight, BlackRook }
600 ChessSquare lionArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackLion, BlackBishop, BlackQueen,
604 BlackKing, BlackBishop, BlackKnight, BlackRook }
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
666 #define GothicArray CapablancaArray
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
677 #define FalconArray CapablancaArray
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695 { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697 { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698 BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699 { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700 WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701 { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702 BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703 { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705 { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
714 Board initialPosition;
717 /* Convert str to a rating. Checks for special cases of "----",
719 "++++", etc. Also strips ()'s */
721 string_to_rating (char *str)
723 while(*str && !isdigit(*str)) ++str;
725 return 0; /* One of the special "no rating" cases */
733 /* Init programStats */
734 programStats.movelist[0] = 0;
735 programStats.depth = 0;
736 programStats.nr_moves = 0;
737 programStats.moves_left = 0;
738 programStats.nodes = 0;
739 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
740 programStats.score = 0;
741 programStats.got_only_move = 0;
742 programStats.got_fail = 0;
743 programStats.line_is_book = 0;
748 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749 if (appData.firstPlaysBlack) {
750 first.twoMachinesColor = "black\n";
751 second.twoMachinesColor = "white\n";
753 first.twoMachinesColor = "white\n";
754 second.twoMachinesColor = "black\n";
757 first.other = &second;
758 second.other = &first;
761 if(appData.timeOddsMode) {
762 norm = appData.timeOdds[0];
763 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
765 first.timeOdds = appData.timeOdds[0]/norm;
766 second.timeOdds = appData.timeOdds[1]/norm;
769 if(programVersion) free(programVersion);
770 if (appData.noChessProgram) {
771 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772 sprintf(programVersion, "%s", PACKAGE_STRING);
774 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
781 UnloadEngine (ChessProgramState *cps)
783 /* Kill off first chess program */
784 if (cps->isr != NULL)
785 RemoveInputSource(cps->isr);
788 if (cps->pr != NoProc) {
790 DoSleep( appData.delayBeforeQuit );
791 SendToProgram("quit\n", cps);
792 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
795 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
799 ClearOptions (ChessProgramState *cps)
802 cps->nrOptions = cps->comboCnt = 0;
803 for(i=0; i<MAX_OPTIONS; i++) {
804 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805 cps->option[i].textValue = 0;
809 char *engineNames[] = {
810 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
819 InitEngine (ChessProgramState *cps, int n)
820 { // [HGM] all engine initialiation put in a function that does one engine
824 cps->which = engineNames[n];
825 cps->maybeThinking = FALSE;
829 cps->sendDrawOffers = 1;
831 cps->program = appData.chessProgram[n];
832 cps->host = appData.host[n];
833 cps->dir = appData.directory[n];
834 cps->initString = appData.engInitString[n];
835 cps->computerString = appData.computerString[n];
836 cps->useSigint = TRUE;
837 cps->useSigterm = TRUE;
838 cps->reuse = appData.reuse[n];
839 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
840 cps->useSetboard = FALSE;
842 cps->usePing = FALSE;
845 cps->usePlayother = FALSE;
846 cps->useColors = TRUE;
847 cps->useUsermove = FALSE;
848 cps->sendICS = FALSE;
849 cps->sendName = appData.icsActive;
850 cps->sdKludge = FALSE;
851 cps->stKludge = FALSE;
852 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853 TidyProgramName(cps->program, cps->host, cps->tidy);
855 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856 cps->analysisSupport = 2; /* detect */
857 cps->analyzing = FALSE;
858 cps->initDone = FALSE;
860 cps->pseudo = appData.pseudo[n];
862 /* New features added by Tord: */
863 cps->useFEN960 = FALSE;
864 cps->useOOCastle = TRUE;
865 /* End of new features added by Tord. */
866 cps->fenOverride = appData.fenOverride[n];
868 /* [HGM] time odds: set factor for each machine */
869 cps->timeOdds = appData.timeOdds[n];
871 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872 cps->accumulateTC = appData.accumulateTC[n];
873 cps->maxNrOfSessions = 1;
878 cps->drawDepth = appData.drawDepth[n];
879 cps->supportsNPS = UNKNOWN;
880 cps->memSize = FALSE;
881 cps->maxCores = FALSE;
882 ASSIGN(cps->egtFormats, "");
885 cps->optionSettings = appData.engOptions[n];
887 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888 cps->isUCI = appData.isUCI[n]; /* [AS] */
889 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
892 if (appData.protocolVersion[n] > PROTOVER
893 || appData.protocolVersion[n] < 1)
898 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899 appData.protocolVersion[n]);
900 if( (len >= MSG_SIZ) && appData.debugMode )
901 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
903 DisplayFatalError(buf, 0, 2);
907 cps->protocolVersion = appData.protocolVersion[n];
910 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
911 ParseFeatures(appData.featureDefaults, cps);
914 ChessProgramState *savCps;
922 if(WaitForEngine(savCps, LoadEngine)) return;
923 CommonEngineInit(); // recalculate time odds
924 if(gameInfo.variant != StringToVariant(appData.variant)) {
925 // we changed variant when loading the engine; this forces us to reset
926 Reset(TRUE, savCps != &first);
927 oldMode = BeginningOfGame; // to prevent restoring old mode
929 InitChessProgram(savCps, FALSE);
930 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931 DisplayMessage("", "");
932 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
936 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
940 ReplaceEngine (ChessProgramState *cps, int n)
942 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
944 if(oldMode != BeginningOfGame) EditGameEvent();
947 appData.noChessProgram = FALSE;
948 appData.clockMode = TRUE;
951 if(n) return; // only startup first engine immediately; second can wait
952 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
959 static char resetOptions[] =
960 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
966 FloatToFront(char **list, char *engineLine)
968 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
970 if(appData.recentEngines <= 0) return;
971 TidyProgramName(engineLine, "localhost", tidy+1);
972 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973 strncpy(buf+1, *list, MSG_SIZ-50);
974 if(p = strstr(buf, tidy)) { // tidy name appears in list
975 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976 while(*p++ = *++q); // squeeze out
978 strcat(tidy, buf+1); // put list behind tidy name
979 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981 ASSIGN(*list, tidy+1);
984 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
987 Load (ChessProgramState *cps, int i)
989 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
992 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
993 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
994 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
995 appData.firstProtocolVersion = PROTOVER;
996 ParseArgsFromString(buf);
998 ReplaceEngine(cps, i);
999 FloatToFront(&appData.recentEngineList, engineLine);
1000 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1004 while(q = strchr(p, SLASH)) p = q+1;
1005 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1006 if(engineDir[0] != NULLCHAR) {
1007 ASSIGN(appData.directory[i], engineDir); p = engineName;
1008 } else if(p != engineName) { // derive directory from engine path, when not given
1010 ASSIGN(appData.directory[i], engineName);
1012 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1013 } else { ASSIGN(appData.directory[i], "."); }
1014 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1016 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1017 snprintf(command, MSG_SIZ, "%s %s", p, params);
1020 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1021 ASSIGN(appData.chessProgram[i], p);
1022 appData.isUCI[i] = isUCI;
1023 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1024 appData.hasOwnBookUCI[i] = hasBook;
1025 if(!nickName[0]) useNick = FALSE;
1026 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1030 q = firstChessProgramNames;
1031 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1032 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1033 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1034 quote, p, quote, appData.directory[i],
1035 useNick ? " -fn \"" : "",
1036 useNick ? nickName : "",
1037 useNick ? "\"" : "",
1038 v1 ? " -firstProtocolVersion 1" : "",
1039 hasBook ? "" : " -fNoOwnBookUCI",
1040 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1041 storeVariant ? " -variant " : "",
1042 storeVariant ? VariantName(gameInfo.variant) : "");
1043 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1044 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1045 if(insert != q) insert[-1] = NULLCHAR;
1046 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1048 FloatToFront(&appData.recentEngineList, buf);
1050 ReplaceEngine(cps, i);
1056 int matched, min, sec;
1058 * Parse timeControl resource
1060 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1061 appData.movesPerSession)) {
1063 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1064 DisplayFatalError(buf, 0, 2);
1068 * Parse searchTime resource
1070 if (*appData.searchTime != NULLCHAR) {
1071 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1073 searchTime = min * 60;
1074 } else if (matched == 2) {
1075 searchTime = min * 60 + sec;
1078 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1079 DisplayFatalError(buf, 0, 2);
1088 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1089 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1091 GetTimeMark(&programStartTime);
1092 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1093 appData.seedBase = random() + (random()<<15);
1094 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1096 ClearProgramStats();
1097 programStats.ok_to_send = 1;
1098 programStats.seen_stat = 0;
1101 * Initialize game list
1107 * Internet chess server status
1109 if (appData.icsActive) {
1110 appData.matchMode = FALSE;
1111 appData.matchGames = 0;
1113 appData.noChessProgram = !appData.zippyPlay;
1115 appData.zippyPlay = FALSE;
1116 appData.zippyTalk = FALSE;
1117 appData.noChessProgram = TRUE;
1119 if (*appData.icsHelper != NULLCHAR) {
1120 appData.useTelnet = TRUE;
1121 appData.telnetProgram = appData.icsHelper;
1124 appData.zippyTalk = appData.zippyPlay = FALSE;
1127 /* [AS] Initialize pv info list [HGM] and game state */
1131 for( i=0; i<=framePtr; i++ ) {
1132 pvInfoList[i].depth = -1;
1133 boards[i][EP_STATUS] = EP_NONE;
1134 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1140 /* [AS] Adjudication threshold */
1141 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1143 InitEngine(&first, 0);
1144 InitEngine(&second, 1);
1147 pairing.which = "pairing"; // pairing engine
1148 pairing.pr = NoProc;
1150 pairing.program = appData.pairingEngine;
1151 pairing.host = "localhost";
1154 if (appData.icsActive) {
1155 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1156 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1157 appData.clockMode = FALSE;
1158 first.sendTime = second.sendTime = 0;
1162 /* Override some settings from environment variables, for backward
1163 compatibility. Unfortunately it's not feasible to have the env
1164 vars just set defaults, at least in xboard. Ugh.
1166 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1171 if (!appData.icsActive) {
1175 /* Check for variants that are supported only in ICS mode,
1176 or not at all. Some that are accepted here nevertheless
1177 have bugs; see comments below.
1179 VariantClass variant = StringToVariant(appData.variant);
1181 case VariantBughouse: /* need four players and two boards */
1182 case VariantKriegspiel: /* need to hide pieces and move details */
1183 /* case VariantFischeRandom: (Fabien: moved below) */
1184 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1185 if( (len >= MSG_SIZ) && appData.debugMode )
1186 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1188 DisplayFatalError(buf, 0, 2);
1191 case VariantUnknown:
1192 case VariantLoadable:
1202 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1203 if( (len >= MSG_SIZ) && appData.debugMode )
1204 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1206 DisplayFatalError(buf, 0, 2);
1209 case VariantNormal: /* definitely works! */
1210 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1211 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1214 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1215 case VariantFairy: /* [HGM] TestLegality definitely off! */
1216 case VariantGothic: /* [HGM] should work */
1217 case VariantCapablanca: /* [HGM] should work */
1218 case VariantCourier: /* [HGM] initial forced moves not implemented */
1219 case VariantShogi: /* [HGM] could still mate with pawn drop */
1220 case VariantChu: /* [HGM] experimental */
1221 case VariantKnightmate: /* [HGM] should work */
1222 case VariantCylinder: /* [HGM] untested */
1223 case VariantFalcon: /* [HGM] untested */
1224 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1225 offboard interposition not understood */
1226 case VariantWildCastle: /* pieces not automatically shuffled */
1227 case VariantNoCastle: /* pieces not automatically shuffled */
1228 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1229 case VariantLosers: /* should work except for win condition,
1230 and doesn't know captures are mandatory */
1231 case VariantSuicide: /* should work except for win condition,
1232 and doesn't know captures are mandatory */
1233 case VariantGiveaway: /* should work except for win condition,
1234 and doesn't know captures are mandatory */
1235 case VariantTwoKings: /* should work */
1236 case VariantAtomic: /* should work except for win condition */
1237 case Variant3Check: /* should work except for win condition */
1238 case VariantShatranj: /* should work except for all win conditions */
1239 case VariantMakruk: /* should work except for draw countdown */
1240 case VariantASEAN : /* should work except for draw countdown */
1241 case VariantBerolina: /* might work if TestLegality is off */
1242 case VariantCapaRandom: /* should work */
1243 case VariantJanus: /* should work */
1244 case VariantSuper: /* experimental */
1245 case VariantGreat: /* experimental, requires legality testing to be off */
1246 case VariantSChess: /* S-Chess, should work */
1247 case VariantGrand: /* should work */
1248 case VariantSpartan: /* should work */
1249 case VariantLion: /* should work */
1250 case VariantChuChess: /* should work */
1258 NextIntegerFromString (char ** str, long * value)
1263 while( *s == ' ' || *s == '\t' ) {
1269 if( *s >= '0' && *s <= '9' ) {
1270 while( *s >= '0' && *s <= '9' ) {
1271 *value = *value * 10 + (*s - '0');
1284 NextTimeControlFromString (char ** str, long * value)
1287 int result = NextIntegerFromString( str, &temp );
1290 *value = temp * 60; /* Minutes */
1291 if( **str == ':' ) {
1293 result = NextIntegerFromString( str, &temp );
1294 *value += temp; /* Seconds */
1302 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1303 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1304 int result = -1, type = 0; long temp, temp2;
1306 if(**str != ':') return -1; // old params remain in force!
1308 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1309 if( NextIntegerFromString( str, &temp ) ) return -1;
1310 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1313 /* time only: incremental or sudden-death time control */
1314 if(**str == '+') { /* increment follows; read it */
1316 if(**str == '!') type = *(*str)++; // Bronstein TC
1317 if(result = NextIntegerFromString( str, &temp2)) return -1;
1318 *inc = temp2 * 1000;
1319 if(**str == '.') { // read fraction of increment
1320 char *start = ++(*str);
1321 if(result = NextIntegerFromString( str, &temp2)) return -1;
1323 while(start++ < *str) temp2 /= 10;
1327 *moves = 0; *tc = temp * 1000; *incType = type;
1331 (*str)++; /* classical time control */
1332 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1344 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1345 { /* [HGM] get time to add from the multi-session time-control string */
1346 int incType, moves=1; /* kludge to force reading of first session */
1347 long time, increment;
1350 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1352 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1353 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1354 if(movenr == -1) return time; /* last move before new session */
1355 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1356 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1357 if(!moves) return increment; /* current session is incremental */
1358 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1359 } while(movenr >= -1); /* try again for next session */
1361 return 0; // no new time quota on this move
1365 ParseTimeControl (char *tc, float ti, int mps)
1369 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1372 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1373 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1374 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1378 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1380 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1383 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1385 snprintf(buf, MSG_SIZ, ":%s", mytc);
1387 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1389 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1394 /* Parse second time control */
1397 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1405 timeControl_2 = tc2 * 1000;
1415 timeControl = tc1 * 1000;
1418 timeIncrement = ti * 1000; /* convert to ms */
1419 movesPerSession = 0;
1422 movesPerSession = mps;
1430 if (appData.debugMode) {
1431 # ifdef __GIT_VERSION
1432 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1434 fprintf(debugFP, "Version: %s\n", programVersion);
1437 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1439 set_cont_sequence(appData.wrapContSeq);
1440 if (appData.matchGames > 0) {
1441 appData.matchMode = TRUE;
1442 } else if (appData.matchMode) {
1443 appData.matchGames = 1;
1445 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1446 appData.matchGames = appData.sameColorGames;
1447 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1448 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1449 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1452 if (appData.noChessProgram || first.protocolVersion == 1) {
1455 /* kludge: allow timeout for initial "feature" commands */
1457 DisplayMessage("", _("Starting chess program"));
1458 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1463 CalculateIndex (int index, int gameNr)
1464 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1466 if(index > 0) return index; // fixed nmber
1467 if(index == 0) return 1;
1468 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1469 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1474 LoadGameOrPosition (int gameNr)
1475 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1476 if (*appData.loadGameFile != NULLCHAR) {
1477 if (!LoadGameFromFile(appData.loadGameFile,
1478 CalculateIndex(appData.loadGameIndex, gameNr),
1479 appData.loadGameFile, FALSE)) {
1480 DisplayFatalError(_("Bad game file"), 0, 1);
1483 } else if (*appData.loadPositionFile != NULLCHAR) {
1484 if (!LoadPositionFromFile(appData.loadPositionFile,
1485 CalculateIndex(appData.loadPositionIndex, gameNr),
1486 appData.loadPositionFile)) {
1487 DisplayFatalError(_("Bad position file"), 0, 1);
1495 ReserveGame (int gameNr, char resChar)
1497 FILE *tf = fopen(appData.tourneyFile, "r+");
1498 char *p, *q, c, buf[MSG_SIZ];
1499 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1500 safeStrCpy(buf, lastMsg, MSG_SIZ);
1501 DisplayMessage(_("Pick new game"), "");
1502 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1503 ParseArgsFromFile(tf);
1504 p = q = appData.results;
1505 if(appData.debugMode) {
1506 char *r = appData.participants;
1507 fprintf(debugFP, "results = '%s'\n", p);
1508 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1509 fprintf(debugFP, "\n");
1511 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1513 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1514 safeStrCpy(q, p, strlen(p) + 2);
1515 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1516 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1517 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1518 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1521 fseek(tf, -(strlen(p)+4), SEEK_END);
1523 if(c != '"') // depending on DOS or Unix line endings we can be one off
1524 fseek(tf, -(strlen(p)+2), SEEK_END);
1525 else fseek(tf, -(strlen(p)+3), SEEK_END);
1526 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1527 DisplayMessage(buf, "");
1528 free(p); appData.results = q;
1529 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1530 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1531 int round = appData.defaultMatchGames * appData.tourneyType;
1532 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1533 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1534 UnloadEngine(&first); // next game belongs to other pairing;
1535 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1537 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1541 MatchEvent (int mode)
1542 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1544 if(matchMode) { // already in match mode: switch it off
1546 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1549 // if(gameMode != BeginningOfGame) {
1550 // DisplayError(_("You can only start a match from the initial position."), 0);
1554 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1555 /* Set up machine vs. machine match */
1557 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1558 if(appData.tourneyFile[0]) {
1560 if(nextGame > appData.matchGames) {
1562 if(strchr(appData.results, '*') == NULL) {
1564 appData.tourneyCycles++;
1565 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1567 NextTourneyGame(-1, &dummy);
1569 if(nextGame <= appData.matchGames) {
1570 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1572 ScheduleDelayedEvent(NextMatchGame, 10000);
1577 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1578 DisplayError(buf, 0);
1579 appData.tourneyFile[0] = 0;
1583 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1584 DisplayFatalError(_("Can't have a match with no chess programs"),
1589 matchGame = roundNr = 1;
1590 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1594 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1597 InitBackEnd3 P((void))
1599 GameMode initialMode;
1603 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1604 !strcmp(appData.variant, "normal") && // no explicit variant request
1605 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1606 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1607 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1608 char c, *q = first.variants, *p = strchr(q, ',');
1609 if(p) *p = NULLCHAR;
1610 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1612 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1613 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1614 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1615 Reset(TRUE, FALSE); // and re-initialize
1620 InitChessProgram(&first, startedFromSetupPosition);
1622 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1623 free(programVersion);
1624 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1625 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1626 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1629 if (appData.icsActive) {
1631 /* [DM] Make a console window if needed [HGM] merged ifs */
1637 if (*appData.icsCommPort != NULLCHAR)
1638 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1639 appData.icsCommPort);
1641 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1642 appData.icsHost, appData.icsPort);
1644 if( (len >= MSG_SIZ) && appData.debugMode )
1645 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647 DisplayFatalError(buf, err, 1);
1652 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1654 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1655 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1656 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1657 } else if (appData.noChessProgram) {
1663 if (*appData.cmailGameName != NULLCHAR) {
1665 OpenLoopback(&cmailPR);
1667 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1671 DisplayMessage("", "");
1672 if (StrCaseCmp(appData.initialMode, "") == 0) {
1673 initialMode = BeginningOfGame;
1674 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1675 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1676 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1677 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1680 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1681 initialMode = TwoMachinesPlay;
1682 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1683 initialMode = AnalyzeFile;
1684 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1685 initialMode = AnalyzeMode;
1686 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1687 initialMode = MachinePlaysWhite;
1688 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1689 initialMode = MachinePlaysBlack;
1690 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1691 initialMode = EditGame;
1692 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1693 initialMode = EditPosition;
1694 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1695 initialMode = Training;
1697 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1698 if( (len >= MSG_SIZ) && appData.debugMode )
1699 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1701 DisplayFatalError(buf, 0, 2);
1705 if (appData.matchMode) {
1706 if(appData.tourneyFile[0]) { // start tourney from command line
1708 if(f = fopen(appData.tourneyFile, "r")) {
1709 ParseArgsFromFile(f); // make sure tourney parmeters re known
1711 appData.clockMode = TRUE;
1713 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1716 } else if (*appData.cmailGameName != NULLCHAR) {
1717 /* Set up cmail mode */
1718 ReloadCmailMsgEvent(TRUE);
1720 /* Set up other modes */
1721 if (initialMode == AnalyzeFile) {
1722 if (*appData.loadGameFile == NULLCHAR) {
1723 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1727 if (*appData.loadGameFile != NULLCHAR) {
1728 (void) LoadGameFromFile(appData.loadGameFile,
1729 appData.loadGameIndex,
1730 appData.loadGameFile, TRUE);
1731 } else if (*appData.loadPositionFile != NULLCHAR) {
1732 (void) LoadPositionFromFile(appData.loadPositionFile,
1733 appData.loadPositionIndex,
1734 appData.loadPositionFile);
1735 /* [HGM] try to make self-starting even after FEN load */
1736 /* to allow automatic setup of fairy variants with wtm */
1737 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1738 gameMode = BeginningOfGame;
1739 setboardSpoiledMachineBlack = 1;
1741 /* [HGM] loadPos: make that every new game uses the setup */
1742 /* from file as long as we do not switch variant */
1743 if(!blackPlaysFirst) {
1744 startedFromPositionFile = TRUE;
1745 CopyBoard(filePosition, boards[0]);
1746 CopyBoard(initialPosition, boards[0]);
1748 } else if(*appData.fen != NULLCHAR) {
1749 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1750 startedFromPositionFile = TRUE;
1754 if (initialMode == AnalyzeMode) {
1755 if (appData.noChessProgram) {
1756 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1759 if (appData.icsActive) {
1760 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1764 } else if (initialMode == AnalyzeFile) {
1765 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1766 ShowThinkingEvent();
1768 AnalysisPeriodicEvent(1);
1769 } else if (initialMode == MachinePlaysWhite) {
1770 if (appData.noChessProgram) {
1771 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1775 if (appData.icsActive) {
1776 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1780 MachineWhiteEvent();
1781 } else if (initialMode == MachinePlaysBlack) {
1782 if (appData.noChessProgram) {
1783 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1787 if (appData.icsActive) {
1788 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1792 MachineBlackEvent();
1793 } else if (initialMode == TwoMachinesPlay) {
1794 if (appData.noChessProgram) {
1795 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1799 if (appData.icsActive) {
1800 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1805 } else if (initialMode == EditGame) {
1807 } else if (initialMode == EditPosition) {
1808 EditPositionEvent();
1809 } else if (initialMode == Training) {
1810 if (*appData.loadGameFile == NULLCHAR) {
1811 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1820 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1822 DisplayBook(current+1);
1824 MoveHistorySet( movelist, first, last, current, pvInfoList );
1826 EvalGraphSet( first, last, current, pvInfoList );
1828 MakeEngineOutputTitle();
1832 * Establish will establish a contact to a remote host.port.
1833 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1834 * used to talk to the host.
1835 * Returns 0 if okay, error code if not.
1842 if (*appData.icsCommPort != NULLCHAR) {
1843 /* Talk to the host through a serial comm port */
1844 return OpenCommPort(appData.icsCommPort, &icsPR);
1846 } else if (*appData.gateway != NULLCHAR) {
1847 if (*appData.remoteShell == NULLCHAR) {
1848 /* Use the rcmd protocol to run telnet program on a gateway host */
1849 snprintf(buf, sizeof(buf), "%s %s %s",
1850 appData.telnetProgram, appData.icsHost, appData.icsPort);
1851 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1854 /* Use the rsh program to run telnet program on a gateway host */
1855 if (*appData.remoteUser == NULLCHAR) {
1856 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1857 appData.gateway, appData.telnetProgram,
1858 appData.icsHost, appData.icsPort);
1860 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1861 appData.remoteShell, appData.gateway,
1862 appData.remoteUser, appData.telnetProgram,
1863 appData.icsHost, appData.icsPort);
1865 return StartChildProcess(buf, "", &icsPR);
1868 } else if (appData.useTelnet) {
1869 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1872 /* TCP socket interface differs somewhat between
1873 Unix and NT; handle details in the front end.
1875 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1880 EscapeExpand (char *p, char *q)
1881 { // [HGM] initstring: routine to shape up string arguments
1882 while(*p++ = *q++) if(p[-1] == '\\')
1884 case 'n': p[-1] = '\n'; break;
1885 case 'r': p[-1] = '\r'; break;
1886 case 't': p[-1] = '\t'; break;
1887 case '\\': p[-1] = '\\'; break;
1888 case 0: *p = 0; return;
1889 default: p[-1] = q[-1]; break;
1894 show_bytes (FILE *fp, char *buf, int count)
1897 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1898 fprintf(fp, "\\%03o", *buf & 0xff);
1907 /* Returns an errno value */
1909 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1911 char buf[8192], *p, *q, *buflim;
1912 int left, newcount, outcount;
1914 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1915 *appData.gateway != NULLCHAR) {
1916 if (appData.debugMode) {
1917 fprintf(debugFP, ">ICS: ");
1918 show_bytes(debugFP, message, count);
1919 fprintf(debugFP, "\n");
1921 return OutputToProcess(pr, message, count, outError);
1924 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1931 if (appData.debugMode) {
1932 fprintf(debugFP, ">ICS: ");
1933 show_bytes(debugFP, buf, newcount);
1934 fprintf(debugFP, "\n");
1936 outcount = OutputToProcess(pr, buf, newcount, outError);
1937 if (outcount < newcount) return -1; /* to be sure */
1944 } else if (((unsigned char) *p) == TN_IAC) {
1945 *q++ = (char) TN_IAC;
1952 if (appData.debugMode) {
1953 fprintf(debugFP, ">ICS: ");
1954 show_bytes(debugFP, buf, newcount);
1955 fprintf(debugFP, "\n");
1957 outcount = OutputToProcess(pr, buf, newcount, outError);
1958 if (outcount < newcount) return -1; /* to be sure */
1963 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1965 int outError, outCount;
1966 static int gotEof = 0;
1969 /* Pass data read from player on to ICS */
1972 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1973 if (outCount < count) {
1974 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1976 if(have_sent_ICS_logon == 2) {
1977 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1978 fprintf(ini, "%s", message);
1979 have_sent_ICS_logon = 3;
1981 have_sent_ICS_logon = 1;
1982 } else if(have_sent_ICS_logon == 3) {
1983 fprintf(ini, "%s", message);
1985 have_sent_ICS_logon = 1;
1987 } else if (count < 0) {
1988 RemoveInputSource(isr);
1989 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1990 } else if (gotEof++ > 0) {
1991 RemoveInputSource(isr);
1992 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1998 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1999 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2000 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2001 SendToICS("date\n");
2002 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2005 /* added routine for printf style output to ics */
2007 ics_printf (char *format, ...)
2009 char buffer[MSG_SIZ];
2012 va_start(args, format);
2013 vsnprintf(buffer, sizeof(buffer), format, args);
2014 buffer[sizeof(buffer)-1] = '\0';
2022 int count, outCount, outError;
2024 if (icsPR == NoProc) return;
2027 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2028 if (outCount < count) {
2029 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2033 /* This is used for sending logon scripts to the ICS. Sending
2034 without a delay causes problems when using timestamp on ICC
2035 (at least on my machine). */
2037 SendToICSDelayed (char *s, long msdelay)
2039 int count, outCount, outError;
2041 if (icsPR == NoProc) return;
2044 if (appData.debugMode) {
2045 fprintf(debugFP, ">ICS: ");
2046 show_bytes(debugFP, s, count);
2047 fprintf(debugFP, "\n");
2049 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2051 if (outCount < count) {
2052 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2057 /* Remove all highlighting escape sequences in s
2058 Also deletes any suffix starting with '('
2061 StripHighlightAndTitle (char *s)
2063 static char retbuf[MSG_SIZ];
2066 while (*s != NULLCHAR) {
2067 while (*s == '\033') {
2068 while (*s != NULLCHAR && !isalpha(*s)) s++;
2069 if (*s != NULLCHAR) s++;
2071 while (*s != NULLCHAR && *s != '\033') {
2072 if (*s == '(' || *s == '[') {
2083 /* Remove all highlighting escape sequences in s */
2085 StripHighlight (char *s)
2087 static char retbuf[MSG_SIZ];
2090 while (*s != NULLCHAR) {
2091 while (*s == '\033') {
2092 while (*s != NULLCHAR && !isalpha(*s)) s++;
2093 if (*s != NULLCHAR) s++;
2095 while (*s != NULLCHAR && *s != '\033') {
2103 char engineVariant[MSG_SIZ];
2104 char *variantNames[] = VARIANT_NAMES;
2106 VariantName (VariantClass v)
2108 if(v == VariantUnknown || *engineVariant) return engineVariant;
2109 return variantNames[v];
2113 /* Identify a variant from the strings the chess servers use or the
2114 PGN Variant tag names we use. */
2116 StringToVariant (char *e)
2120 VariantClass v = VariantNormal;
2121 int i, found = FALSE;
2122 char buf[MSG_SIZ], c;
2127 /* [HGM] skip over optional board-size prefixes */
2128 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2129 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2130 while( *e++ != '_');
2133 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2137 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2138 if (p = StrCaseStr(e, variantNames[i])) {
2139 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2140 v = (VariantClass) i;
2147 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2148 || StrCaseStr(e, "wild/fr")
2149 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2150 v = VariantFischeRandom;
2151 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2152 (i = 1, p = StrCaseStr(e, "w"))) {
2154 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2161 case 0: /* FICS only, actually */
2163 /* Castling legal even if K starts on d-file */
2164 v = VariantWildCastle;
2169 /* Castling illegal even if K & R happen to start in
2170 normal positions. */
2171 v = VariantNoCastle;
2184 /* Castling legal iff K & R start in normal positions */
2190 /* Special wilds for position setup; unclear what to do here */
2191 v = VariantLoadable;
2194 /* Bizarre ICC game */
2195 v = VariantTwoKings;
2198 v = VariantKriegspiel;
2204 v = VariantFischeRandom;
2207 v = VariantCrazyhouse;
2210 v = VariantBughouse;
2216 /* Not quite the same as FICS suicide! */
2217 v = VariantGiveaway;
2223 v = VariantShatranj;
2226 /* Temporary names for future ICC types. The name *will* change in
2227 the next xboard/WinBoard release after ICC defines it. */
2265 v = VariantCapablanca;
2268 v = VariantKnightmate;
2274 v = VariantCylinder;
2280 v = VariantCapaRandom;
2283 v = VariantBerolina;
2295 /* Found "wild" or "w" in the string but no number;
2296 must assume it's normal chess. */
2300 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2301 if( (len >= MSG_SIZ) && appData.debugMode )
2302 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2304 DisplayError(buf, 0);
2310 if (appData.debugMode) {
2311 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2312 e, wnum, VariantName(v));
2317 static int leftover_start = 0, leftover_len = 0;
2318 char star_match[STAR_MATCH_N][MSG_SIZ];
2320 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2321 advance *index beyond it, and set leftover_start to the new value of
2322 *index; else return FALSE. If pattern contains the character '*', it
2323 matches any sequence of characters not containing '\r', '\n', or the
2324 character following the '*' (if any), and the matched sequence(s) are
2325 copied into star_match.
2328 looking_at ( char *buf, int *index, char *pattern)
2330 char *bufp = &buf[*index], *patternp = pattern;
2332 char *matchp = star_match[0];
2335 if (*patternp == NULLCHAR) {
2336 *index = leftover_start = bufp - buf;
2340 if (*bufp == NULLCHAR) return FALSE;
2341 if (*patternp == '*') {
2342 if (*bufp == *(patternp + 1)) {
2344 matchp = star_match[++star_count];
2348 } else if (*bufp == '\n' || *bufp == '\r') {
2350 if (*patternp == NULLCHAR)
2355 *matchp++ = *bufp++;
2359 if (*patternp != *bufp) return FALSE;
2366 SendToPlayer (char *data, int length)
2368 int error, outCount;
2369 outCount = OutputToProcess(NoProc, data, length, &error);
2370 if (outCount < length) {
2371 DisplayFatalError(_("Error writing to display"), error, 1);
2376 PackHolding (char packed[], char *holding)
2386 switch (runlength) {
2397 sprintf(q, "%d", runlength);
2409 /* Telnet protocol requests from the front end */
2411 TelnetRequest (unsigned char ddww, unsigned char option)
2413 unsigned char msg[3];
2414 int outCount, outError;
2416 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2418 if (appData.debugMode) {
2419 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2435 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2444 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2447 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2452 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2454 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2461 if (!appData.icsActive) return;
2462 TelnetRequest(TN_DO, TN_ECHO);
2468 if (!appData.icsActive) return;
2469 TelnetRequest(TN_DONT, TN_ECHO);
2473 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2475 /* put the holdings sent to us by the server on the board holdings area */
2476 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2480 if(gameInfo.holdingsWidth < 2) return;
2481 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2482 return; // prevent overwriting by pre-board holdings
2484 if( (int)lowestPiece >= BlackPawn ) {
2487 holdingsStartRow = BOARD_HEIGHT-1;
2490 holdingsColumn = BOARD_WIDTH-1;
2491 countsColumn = BOARD_WIDTH-2;
2492 holdingsStartRow = 0;
2496 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2497 board[i][holdingsColumn] = EmptySquare;
2498 board[i][countsColumn] = (ChessSquare) 0;
2500 while( (p=*holdings++) != NULLCHAR ) {
2501 piece = CharToPiece( ToUpper(p) );
2502 if(piece == EmptySquare) continue;
2503 /*j = (int) piece - (int) WhitePawn;*/
2504 j = PieceToNumber(piece);
2505 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2506 if(j < 0) continue; /* should not happen */
2507 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2508 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2509 board[holdingsStartRow+j*direction][countsColumn]++;
2515 VariantSwitch (Board board, VariantClass newVariant)
2517 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2518 static Board oldBoard;
2520 startedFromPositionFile = FALSE;
2521 if(gameInfo.variant == newVariant) return;
2523 /* [HGM] This routine is called each time an assignment is made to
2524 * gameInfo.variant during a game, to make sure the board sizes
2525 * are set to match the new variant. If that means adding or deleting
2526 * holdings, we shift the playing board accordingly
2527 * This kludge is needed because in ICS observe mode, we get boards
2528 * of an ongoing game without knowing the variant, and learn about the
2529 * latter only later. This can be because of the move list we requested,
2530 * in which case the game history is refilled from the beginning anyway,
2531 * but also when receiving holdings of a crazyhouse game. In the latter
2532 * case we want to add those holdings to the already received position.
2536 if (appData.debugMode) {
2537 fprintf(debugFP, "Switch board from %s to %s\n",
2538 VariantName(gameInfo.variant), VariantName(newVariant));
2539 setbuf(debugFP, NULL);
2541 shuffleOpenings = 0; /* [HGM] shuffle */
2542 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2546 newWidth = 9; newHeight = 9;
2547 gameInfo.holdingsSize = 7;
2548 case VariantBughouse:
2549 case VariantCrazyhouse:
2550 newHoldingsWidth = 2; break;
2554 newHoldingsWidth = 2;
2555 gameInfo.holdingsSize = 8;
2558 case VariantCapablanca:
2559 case VariantCapaRandom:
2562 newHoldingsWidth = gameInfo.holdingsSize = 0;
2565 if(newWidth != gameInfo.boardWidth ||
2566 newHeight != gameInfo.boardHeight ||
2567 newHoldingsWidth != gameInfo.holdingsWidth ) {
2569 /* shift position to new playing area, if needed */
2570 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2571 for(i=0; i<BOARD_HEIGHT; i++)
2572 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2573 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575 for(i=0; i<newHeight; i++) {
2576 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2577 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2579 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2580 for(i=0; i<BOARD_HEIGHT; i++)
2581 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2582 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2585 board[HOLDINGS_SET] = 0;
2586 gameInfo.boardWidth = newWidth;
2587 gameInfo.boardHeight = newHeight;
2588 gameInfo.holdingsWidth = newHoldingsWidth;
2589 gameInfo.variant = newVariant;
2590 InitDrawingSizes(-2, 0);
2591 } else gameInfo.variant = newVariant;
2592 CopyBoard(oldBoard, board); // remember correctly formatted board
2593 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2594 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2597 static int loggedOn = FALSE;
2599 /*-- Game start info cache: --*/
2601 char gs_kind[MSG_SIZ];
2602 static char player1Name[128] = "";
2603 static char player2Name[128] = "";
2604 static char cont_seq[] = "\n\\ ";
2605 static int player1Rating = -1;
2606 static int player2Rating = -1;
2607 /*----------------------------*/
2609 ColorClass curColor = ColorNormal;
2610 int suppressKibitz = 0;
2613 Boolean soughtPending = FALSE;
2614 Boolean seekGraphUp;
2615 #define MAX_SEEK_ADS 200
2617 char *seekAdList[MAX_SEEK_ADS];
2618 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2619 float tcList[MAX_SEEK_ADS];
2620 char colorList[MAX_SEEK_ADS];
2621 int nrOfSeekAds = 0;
2622 int minRating = 1010, maxRating = 2800;
2623 int hMargin = 10, vMargin = 20, h, w;
2624 extern int squareSize, lineGap;
2629 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2630 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2631 if(r < minRating+100 && r >=0 ) r = minRating+100;
2632 if(r > maxRating) r = maxRating;
2633 if(tc < 1.f) tc = 1.f;
2634 if(tc > 95.f) tc = 95.f;
2635 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2636 y = ((double)r - minRating)/(maxRating - minRating)
2637 * (h-vMargin-squareSize/8-1) + vMargin;
2638 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2639 if(strstr(seekAdList[i], " u ")) color = 1;
2640 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2641 !strstr(seekAdList[i], "bullet") &&
2642 !strstr(seekAdList[i], "blitz") &&
2643 !strstr(seekAdList[i], "standard") ) color = 2;
2644 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2645 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2649 PlotSingleSeekAd (int i)
2655 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2657 char buf[MSG_SIZ], *ext = "";
2658 VariantClass v = StringToVariant(type);
2659 if(strstr(type, "wild")) {
2660 ext = type + 4; // append wild number
2661 if(v == VariantFischeRandom) type = "chess960"; else
2662 if(v == VariantLoadable) type = "setup"; else
2663 type = VariantName(v);
2665 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2666 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2667 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2668 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2669 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2670 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2671 seekNrList[nrOfSeekAds] = nr;
2672 zList[nrOfSeekAds] = 0;
2673 seekAdList[nrOfSeekAds++] = StrSave(buf);
2674 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2679 EraseSeekDot (int i)
2681 int x = xList[i], y = yList[i], d=squareSize/4, k;
2682 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2683 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2684 // now replot every dot that overlapped
2685 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2686 int xx = xList[k], yy = yList[k];
2687 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2688 DrawSeekDot(xx, yy, colorList[k]);
2693 RemoveSeekAd (int nr)
2696 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2698 if(seekAdList[i]) free(seekAdList[i]);
2699 seekAdList[i] = seekAdList[--nrOfSeekAds];
2700 seekNrList[i] = seekNrList[nrOfSeekAds];
2701 ratingList[i] = ratingList[nrOfSeekAds];
2702 colorList[i] = colorList[nrOfSeekAds];
2703 tcList[i] = tcList[nrOfSeekAds];
2704 xList[i] = xList[nrOfSeekAds];
2705 yList[i] = yList[nrOfSeekAds];
2706 zList[i] = zList[nrOfSeekAds];
2707 seekAdList[nrOfSeekAds] = NULL;
2713 MatchSoughtLine (char *line)
2715 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2716 int nr, base, inc, u=0; char dummy;
2718 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2719 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2721 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2722 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2723 // match: compact and save the line
2724 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2734 if(!seekGraphUp) return FALSE;
2735 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2736 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2738 DrawSeekBackground(0, 0, w, h);
2739 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2740 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2741 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2742 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2744 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2747 snprintf(buf, MSG_SIZ, "%d", i);
2748 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2751 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2752 for(i=1; i<100; i+=(i<10?1:5)) {
2753 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2754 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2755 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2757 snprintf(buf, MSG_SIZ, "%d", i);
2758 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2761 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2766 SeekGraphClick (ClickType click, int x, int y, int moving)
2768 static int lastDown = 0, displayed = 0, lastSecond;
2769 if(y < 0) return FALSE;
2770 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2771 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2772 if(!seekGraphUp) return FALSE;
2773 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2774 DrawPosition(TRUE, NULL);
2777 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2778 if(click == Release || moving) return FALSE;
2780 soughtPending = TRUE;
2781 SendToICS(ics_prefix);
2782 SendToICS("sought\n"); // should this be "sought all"?
2783 } else { // issue challenge based on clicked ad
2784 int dist = 10000; int i, closest = 0, second = 0;
2785 for(i=0; i<nrOfSeekAds; i++) {
2786 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2787 if(d < dist) { dist = d; closest = i; }
2788 second += (d - zList[i] < 120); // count in-range ads
2789 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2793 second = (second > 1);
2794 if(displayed != closest || second != lastSecond) {
2795 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2796 lastSecond = second; displayed = closest;
2798 if(click == Press) {
2799 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2802 } // on press 'hit', only show info
2803 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2804 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2805 SendToICS(ics_prefix);
2807 return TRUE; // let incoming board of started game pop down the graph
2808 } else if(click == Release) { // release 'miss' is ignored
2809 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2810 if(moving == 2) { // right up-click
2811 nrOfSeekAds = 0; // refresh graph
2812 soughtPending = TRUE;
2813 SendToICS(ics_prefix);
2814 SendToICS("sought\n"); // should this be "sought all"?
2817 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2818 // press miss or release hit 'pop down' seek graph
2819 seekGraphUp = FALSE;
2820 DrawPosition(TRUE, NULL);
2826 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2828 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2829 #define STARTED_NONE 0
2830 #define STARTED_MOVES 1
2831 #define STARTED_BOARD 2
2832 #define STARTED_OBSERVE 3
2833 #define STARTED_HOLDINGS 4
2834 #define STARTED_CHATTER 5
2835 #define STARTED_COMMENT 6
2836 #define STARTED_MOVES_NOHIDE 7
2838 static int started = STARTED_NONE;
2839 static char parse[20000];
2840 static int parse_pos = 0;
2841 static char buf[BUF_SIZE + 1];
2842 static int firstTime = TRUE, intfSet = FALSE;
2843 static ColorClass prevColor = ColorNormal;
2844 static int savingComment = FALSE;
2845 static int cmatch = 0; // continuation sequence match
2852 int backup; /* [DM] For zippy color lines */
2854 char talker[MSG_SIZ]; // [HGM] chat
2855 int channel, collective=0;
2857 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2859 if (appData.debugMode) {
2861 fprintf(debugFP, "<ICS: ");
2862 show_bytes(debugFP, data, count);
2863 fprintf(debugFP, "\n");
2867 if (appData.debugMode) { int f = forwardMostMove;
2868 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2869 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2870 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2873 /* If last read ended with a partial line that we couldn't parse,
2874 prepend it to the new read and try again. */
2875 if (leftover_len > 0) {
2876 for (i=0; i<leftover_len; i++)
2877 buf[i] = buf[leftover_start + i];
2880 /* copy new characters into the buffer */
2881 bp = buf + leftover_len;
2882 buf_len=leftover_len;
2883 for (i=0; i<count; i++)
2886 if (data[i] == '\r')
2889 // join lines split by ICS?
2890 if (!appData.noJoin)
2893 Joining just consists of finding matches against the
2894 continuation sequence, and discarding that sequence
2895 if found instead of copying it. So, until a match
2896 fails, there's nothing to do since it might be the
2897 complete sequence, and thus, something we don't want
2900 if (data[i] == cont_seq[cmatch])
2903 if (cmatch == strlen(cont_seq))
2905 cmatch = 0; // complete match. just reset the counter
2908 it's possible for the ICS to not include the space
2909 at the end of the last word, making our [correct]
2910 join operation fuse two separate words. the server
2911 does this when the space occurs at the width setting.
2913 if (!buf_len || buf[buf_len-1] != ' ')
2924 match failed, so we have to copy what matched before
2925 falling through and copying this character. In reality,
2926 this will only ever be just the newline character, but
2927 it doesn't hurt to be precise.
2929 strncpy(bp, cont_seq, cmatch);
2941 buf[buf_len] = NULLCHAR;
2942 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2947 while (i < buf_len) {
2948 /* Deal with part of the TELNET option negotiation
2949 protocol. We refuse to do anything beyond the
2950 defaults, except that we allow the WILL ECHO option,
2951 which ICS uses to turn off password echoing when we are
2952 directly connected to it. We reject this option
2953 if localLineEditing mode is on (always on in xboard)
2954 and we are talking to port 23, which might be a real
2955 telnet server that will try to keep WILL ECHO on permanently.
2957 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2958 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2959 unsigned char option;
2961 switch ((unsigned char) buf[++i]) {
2963 if (appData.debugMode)
2964 fprintf(debugFP, "\n<WILL ");
2965 switch (option = (unsigned char) buf[++i]) {
2967 if (appData.debugMode)
2968 fprintf(debugFP, "ECHO ");
2969 /* Reply only if this is a change, according
2970 to the protocol rules. */
2971 if (remoteEchoOption) break;
2972 if (appData.localLineEditing &&
2973 atoi(appData.icsPort) == TN_PORT) {
2974 TelnetRequest(TN_DONT, TN_ECHO);
2977 TelnetRequest(TN_DO, TN_ECHO);
2978 remoteEchoOption = TRUE;
2982 if (appData.debugMode)
2983 fprintf(debugFP, "%d ", option);
2984 /* Whatever this is, we don't want it. */
2985 TelnetRequest(TN_DONT, option);
2990 if (appData.debugMode)
2991 fprintf(debugFP, "\n<WONT ");
2992 switch (option = (unsigned char) buf[++i]) {
2994 if (appData.debugMode)
2995 fprintf(debugFP, "ECHO ");
2996 /* Reply only if this is a change, according
2997 to the protocol rules. */
2998 if (!remoteEchoOption) break;
3000 TelnetRequest(TN_DONT, TN_ECHO);
3001 remoteEchoOption = FALSE;
3004 if (appData.debugMode)
3005 fprintf(debugFP, "%d ", (unsigned char) option);
3006 /* Whatever this is, it must already be turned
3007 off, because we never agree to turn on
3008 anything non-default, so according to the
3009 protocol rules, we don't reply. */
3014 if (appData.debugMode)
3015 fprintf(debugFP, "\n<DO ");
3016 switch (option = (unsigned char) buf[++i]) {
3018 /* Whatever this is, we refuse to do it. */
3019 if (appData.debugMode)
3020 fprintf(debugFP, "%d ", option);
3021 TelnetRequest(TN_WONT, option);
3026 if (appData.debugMode)
3027 fprintf(debugFP, "\n<DONT ");
3028 switch (option = (unsigned char) buf[++i]) {
3030 if (appData.debugMode)
3031 fprintf(debugFP, "%d ", option);
3032 /* Whatever this is, we are already not doing
3033 it, because we never agree to do anything
3034 non-default, so according to the protocol
3035 rules, we don't reply. */
3040 if (appData.debugMode)
3041 fprintf(debugFP, "\n<IAC ");
3042 /* Doubled IAC; pass it through */
3046 if (appData.debugMode)
3047 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3048 /* Drop all other telnet commands on the floor */
3051 if (oldi > next_out)
3052 SendToPlayer(&buf[next_out], oldi - next_out);
3058 /* OK, this at least will *usually* work */
3059 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3063 if (loggedOn && !intfSet) {
3064 if (ics_type == ICS_ICC) {
3065 snprintf(str, MSG_SIZ,
3066 "/set-quietly interface %s\n/set-quietly style 12\n",
3068 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069 strcat(str, "/set-2 51 1\n/set seek 1\n");
3070 } else if (ics_type == ICS_CHESSNET) {
3071 snprintf(str, MSG_SIZ, "/style 12\n");
3073 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3074 strcat(str, programVersion);
3075 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3076 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3077 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3079 strcat(str, "$iset nohighlight 1\n");
3081 strcat(str, "$iset lock 1\n$style 12\n");
3084 NotifyFrontendLogin();
3088 if (started == STARTED_COMMENT) {
3089 /* Accumulate characters in comment */
3090 parse[parse_pos++] = buf[i];
3091 if (buf[i] == '\n') {
3092 parse[parse_pos] = NULLCHAR;
3093 if(chattingPartner>=0) {
3095 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3096 OutputChatMessage(chattingPartner, mess);
3097 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3099 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3100 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3101 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3102 OutputChatMessage(p, mess);
3106 chattingPartner = -1;
3107 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3110 if(!suppressKibitz) // [HGM] kibitz
3111 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3112 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3113 int nrDigit = 0, nrAlph = 0, j;
3114 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3115 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3116 parse[parse_pos] = NULLCHAR;
3117 // try to be smart: if it does not look like search info, it should go to
3118 // ICS interaction window after all, not to engine-output window.
3119 for(j=0; j<parse_pos; j++) { // count letters and digits
3120 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3121 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3122 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3124 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3125 int depth=0; float score;
3126 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3127 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3128 pvInfoList[forwardMostMove-1].depth = depth;
3129 pvInfoList[forwardMostMove-1].score = 100*score;
3131 OutputKibitz(suppressKibitz, parse);
3134 if(gameMode == IcsObserving) // restore original ICS messages
3135 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3136 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3138 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3139 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3140 SendToPlayer(tmp, strlen(tmp));
3142 next_out = i+1; // [HGM] suppress printing in ICS window
3144 started = STARTED_NONE;
3146 /* Don't match patterns against characters in comment */
3151 if (started == STARTED_CHATTER) {
3152 if (buf[i] != '\n') {
3153 /* Don't match patterns against characters in chatter */
3157 started = STARTED_NONE;
3158 if(suppressKibitz) next_out = i+1;
3161 /* Kludge to deal with rcmd protocol */
3162 if (firstTime && looking_at(buf, &i, "\001*")) {
3163 DisplayFatalError(&buf[1], 0, 1);
3169 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3172 if (appData.debugMode)
3173 fprintf(debugFP, "ics_type %d\n", ics_type);
3176 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3177 ics_type = ICS_FICS;
3179 if (appData.debugMode)
3180 fprintf(debugFP, "ics_type %d\n", ics_type);
3183 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3184 ics_type = ICS_CHESSNET;
3186 if (appData.debugMode)
3187 fprintf(debugFP, "ics_type %d\n", ics_type);
3192 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3193 looking_at(buf, &i, "Logging you in as \"*\"") ||
3194 looking_at(buf, &i, "will be \"*\""))) {
3195 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3199 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3201 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3202 DisplayIcsInteractionTitle(buf);
3203 have_set_title = TRUE;
3206 /* skip finger notes */
3207 if (started == STARTED_NONE &&
3208 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3209 (buf[i] == '1' && buf[i+1] == '0')) &&
3210 buf[i+2] == ':' && buf[i+3] == ' ') {
3211 started = STARTED_CHATTER;
3217 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3218 if(appData.seekGraph) {
3219 if(soughtPending && MatchSoughtLine(buf+i)) {
3220 i = strstr(buf+i, "rated") - buf;
3221 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222 next_out = leftover_start = i;
3223 started = STARTED_CHATTER;
3224 suppressKibitz = TRUE;
3227 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3228 && looking_at(buf, &i, "* ads displayed")) {
3229 soughtPending = FALSE;
3234 if(appData.autoRefresh) {
3235 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3236 int s = (ics_type == ICS_ICC); // ICC format differs
3238 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3239 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3240 looking_at(buf, &i, "*% "); // eat prompt
3241 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3242 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243 next_out = i; // suppress
3246 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3247 char *p = star_match[0];
3249 if(seekGraphUp) RemoveSeekAd(atoi(p));
3250 while(*p && *p++ != ' '); // next
3252 looking_at(buf, &i, "*% "); // eat prompt
3253 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3260 /* skip formula vars */
3261 if (started == STARTED_NONE &&
3262 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3263 started = STARTED_CHATTER;
3268 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3269 if (appData.autoKibitz && started == STARTED_NONE &&
3270 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3271 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3272 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3273 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3274 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3275 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3276 suppressKibitz = TRUE;
3277 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3279 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3280 && (gameMode == IcsPlayingWhite)) ||
3281 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3282 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3283 started = STARTED_CHATTER; // own kibitz we simply discard
3285 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3286 parse_pos = 0; parse[0] = NULLCHAR;
3287 savingComment = TRUE;
3288 suppressKibitz = gameMode != IcsObserving ? 2 :
3289 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3293 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3294 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3295 && atoi(star_match[0])) {
3296 // suppress the acknowledgements of our own autoKibitz
3298 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3299 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3300 SendToPlayer(star_match[0], strlen(star_match[0]));
3301 if(looking_at(buf, &i, "*% ")) // eat prompt
3302 suppressKibitz = FALSE;
3306 } // [HGM] kibitz: end of patch
3308 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3310 // [HGM] chat: intercept tells by users for which we have an open chat window
3312 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3313 looking_at(buf, &i, "* whispers:") ||
3314 looking_at(buf, &i, "* kibitzes:") ||
3315 looking_at(buf, &i, "* shouts:") ||
3316 looking_at(buf, &i, "* c-shouts:") ||
3317 looking_at(buf, &i, "--> * ") ||
3318 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3319 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3320 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3321 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3323 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3324 chattingPartner = -1; collective = 0;
3326 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3327 for(p=0; p<MAX_CHAT; p++) {
3329 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3330 talker[0] = '['; strcat(talker, "] ");
3331 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3332 chattingPartner = p; break;
3335 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3336 for(p=0; p<MAX_CHAT; p++) {
3338 if(!strcmp("kibitzes", chatPartner[p])) {
3339 talker[0] = '['; strcat(talker, "] ");
3340 chattingPartner = p; break;
3343 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3344 for(p=0; p<MAX_CHAT; p++) {
3346 if(!strcmp("whispers", chatPartner[p])) {
3347 talker[0] = '['; strcat(talker, "] ");
3348 chattingPartner = p; break;
3351 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3352 if(buf[i-8] == '-' && buf[i-3] == 't')
3353 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3355 if(!strcmp("c-shouts", chatPartner[p])) {
3356 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3357 chattingPartner = p; break;
3360 if(chattingPartner < 0)
3361 for(p=0; p<MAX_CHAT; p++) {
3363 if(!strcmp("shouts", chatPartner[p])) {
3364 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3365 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3366 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3367 chattingPartner = p; break;
3371 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3372 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3374 Colorize(ColorTell, FALSE);
3375 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3377 chattingPartner = p; break;
3379 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3380 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3381 started = STARTED_COMMENT;
3382 parse_pos = 0; parse[0] = NULLCHAR;
3383 savingComment = 3 + chattingPartner; // counts as TRUE
3384 if(collective == 3) i = oldi; else {
3385 suppressKibitz = TRUE;
3386 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3387 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3391 } // [HGM] chat: end of patch
3394 if (appData.zippyTalk || appData.zippyPlay) {
3395 /* [DM] Backup address for color zippy lines */
3397 if (loggedOn == TRUE)
3398 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3399 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3402 } // [DM] 'else { ' deleted
3404 /* Regular tells and says */
3405 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3406 looking_at(buf, &i, "* (your partner) tells you: ") ||
3407 looking_at(buf, &i, "* says: ") ||
3408 /* Don't color "message" or "messages" output */
3409 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3410 looking_at(buf, &i, "*. * at *:*: ") ||
3411 looking_at(buf, &i, "--* (*:*): ") ||
3412 /* Message notifications (same color as tells) */
3413 looking_at(buf, &i, "* has left a message ") ||
3414 looking_at(buf, &i, "* just sent you a message:\n") ||
3415 /* Whispers and kibitzes */
3416 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3417 looking_at(buf, &i, "* kibitzes: ") ||
3419 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3421 if (tkind == 1 && strchr(star_match[0], ':')) {
3422 /* Avoid "tells you:" spoofs in channels */
3425 if (star_match[0][0] == NULLCHAR ||
3426 strchr(star_match[0], ' ') ||
3427 (tkind == 3 && strchr(star_match[1], ' '))) {