2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
272 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border; /* [HGM] width of board rim, needed to size seek graph */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
302 /* States for ics_getting_history */
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
310 /* whosays values for GameEnds */
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
322 /* Different types of move when calling RegisterMove */
324 #define CMAIL_RESIGN 1
326 #define CMAIL_ACCEPT 3
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
333 /* Telnet protocol constants */
344 safeStrCpy (char *dst, const char *src, size_t count)
347 assert( dst != NULL );
348 assert( src != NULL );
351 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352 if( i == count && dst[count-1] != NULLCHAR)
354 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355 if(appData.debugMode)
356 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
362 /* Some compiler can't cast u64 to double
363 * This function do the job for us:
365 * We use the highest bit for cast, this only
366 * works if the highest bit is not
367 * in use (This should not happen)
369 * We used this for all compiler
372 u64ToDouble (u64 value)
375 u64 tmp = value & u64Const(0x7fffffffffffffff);
376 r = (double)(s64)tmp;
377 if (value & u64Const(0x8000000000000000))
378 r += 9.2233720368547758080e18; /* 2^63 */
382 /* Fake up flags for now, as we aren't keeping track of castling
383 availability yet. [HGM] Change of logic: the flag now only
384 indicates the type of castlings allowed by the rule of the game.
385 The actual rights themselves are maintained in the array
386 castlingRights, as part of the game history, and are not probed
392 int flags = F_ALL_CASTLE_OK;
393 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394 switch (gameInfo.variant) {
396 flags &= ~F_ALL_CASTLE_OK;
397 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398 flags |= F_IGNORE_CHECK;
400 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
403 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405 case VariantKriegspiel:
406 flags |= F_KRIEGSPIEL_CAPTURE;
408 case VariantCapaRandom:
409 case VariantFischeRandom:
410 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411 case VariantNoCastle:
412 case VariantShatranj:
417 flags &= ~F_ALL_CASTLE_OK;
420 case VariantChuChess:
422 flags |= F_NULL_MOVE;
427 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
435 [AS] Note: sometimes, the sscanf() function is used to parse the input
436 into a fixed-size buffer. Because of this, we must be prepared to
437 receive strings as long as the size of the input buffer, which is currently
438 set to 4K for Windows and 8K for the rest.
439 So, we must either allocate sufficiently large buffers here, or
440 reduce the size of the input buffer in the input reading part.
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
448 ChessProgramState first, second, pairing;
450 /* premove variables */
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
491 int have_sent_ICS_logon = 0;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
505 /* animateTraining preserves the state of appData.animate
506 * when Training mode is activated. This allows the
507 * response to be animated when appData.animate == TRUE and
508 * appData.animateDragging == TRUE.
510 Boolean animateTraining;
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int initialRulePlies, FENrulePlies;
522 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
543 ChessSquare FIDEArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547 BlackKing, BlackBishop, BlackKnight, BlackRook }
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554 BlackKing, BlackKing, BlackKnight, BlackRook }
557 ChessSquare KnightmateArray[2][BOARD_FILES] = {
558 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560 { BlackRook, BlackMan, BlackBishop, BlackQueen,
561 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackMan, BlackFerz,
589 BlackKing, BlackMan, BlackKnight, BlackRook }
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackMan, BlackFerz,
596 BlackKing, BlackMan, BlackKnight, BlackRook }
599 ChessSquare lionArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackLion, BlackBishop, BlackQueen,
603 BlackKing, BlackBishop, BlackKnight, BlackRook }
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
665 #define GothicArray CapablancaArray
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
676 #define FalconArray CapablancaArray
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
713 Board initialPosition;
716 /* Convert str to a rating. Checks for special cases of "----",
718 "++++", etc. Also strips ()'s */
720 string_to_rating (char *str)
722 while(*str && !isdigit(*str)) ++str;
724 return 0; /* One of the special "no rating" cases */
732 /* Init programStats */
733 programStats.movelist[0] = 0;
734 programStats.depth = 0;
735 programStats.nr_moves = 0;
736 programStats.moves_left = 0;
737 programStats.nodes = 0;
738 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
739 programStats.score = 0;
740 programStats.got_only_move = 0;
741 programStats.got_fail = 0;
742 programStats.line_is_book = 0;
747 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748 if (appData.firstPlaysBlack) {
749 first.twoMachinesColor = "black\n";
750 second.twoMachinesColor = "white\n";
752 first.twoMachinesColor = "white\n";
753 second.twoMachinesColor = "black\n";
756 first.other = &second;
757 second.other = &first;
760 if(appData.timeOddsMode) {
761 norm = appData.timeOdds[0];
762 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764 first.timeOdds = appData.timeOdds[0]/norm;
765 second.timeOdds = appData.timeOdds[1]/norm;
768 if(programVersion) free(programVersion);
769 if (appData.noChessProgram) {
770 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771 sprintf(programVersion, "%s", PACKAGE_STRING);
773 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
780 UnloadEngine (ChessProgramState *cps)
782 /* Kill off first chess program */
783 if (cps->isr != NULL)
784 RemoveInputSource(cps->isr);
787 if (cps->pr != NoProc) {
789 DoSleep( appData.delayBeforeQuit );
790 SendToProgram("quit\n", cps);
791 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
794 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
798 ClearOptions (ChessProgramState *cps)
801 cps->nrOptions = cps->comboCnt = 0;
802 for(i=0; i<MAX_OPTIONS; i++) {
803 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804 cps->option[i].textValue = 0;
808 char *engineNames[] = {
809 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
818 InitEngine (ChessProgramState *cps, int n)
819 { // [HGM] all engine initialiation put in a function that does one engine
823 cps->which = engineNames[n];
824 cps->maybeThinking = FALSE;
828 cps->sendDrawOffers = 1;
830 cps->program = appData.chessProgram[n];
831 cps->host = appData.host[n];
832 cps->dir = appData.directory[n];
833 cps->initString = appData.engInitString[n];
834 cps->computerString = appData.computerString[n];
835 cps->useSigint = TRUE;
836 cps->useSigterm = TRUE;
837 cps->reuse = appData.reuse[n];
838 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
839 cps->useSetboard = FALSE;
841 cps->usePing = FALSE;
844 cps->usePlayother = FALSE;
845 cps->useColors = TRUE;
846 cps->useUsermove = FALSE;
847 cps->sendICS = FALSE;
848 cps->sendName = appData.icsActive;
849 cps->sdKludge = FALSE;
850 cps->stKludge = FALSE;
851 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852 TidyProgramName(cps->program, cps->host, cps->tidy);
854 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855 cps->analysisSupport = 2; /* detect */
856 cps->analyzing = FALSE;
857 cps->initDone = FALSE;
859 cps->pseudo = appData.pseudo[n];
861 /* New features added by Tord: */
862 cps->useFEN960 = FALSE;
863 cps->useOOCastle = TRUE;
864 /* End of new features added by Tord. */
865 cps->fenOverride = appData.fenOverride[n];
867 /* [HGM] time odds: set factor for each machine */
868 cps->timeOdds = appData.timeOdds[n];
870 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871 cps->accumulateTC = appData.accumulateTC[n];
872 cps->maxNrOfSessions = 1;
877 cps->drawDepth = appData.drawDepth[n];
878 cps->supportsNPS = UNKNOWN;
879 cps->memSize = FALSE;
880 cps->maxCores = FALSE;
881 ASSIGN(cps->egtFormats, "");
884 cps->optionSettings = appData.engOptions[n];
886 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887 cps->isUCI = appData.isUCI[n]; /* [AS] */
888 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
891 if (appData.protocolVersion[n] > PROTOVER
892 || appData.protocolVersion[n] < 1)
897 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898 appData.protocolVersion[n]);
899 if( (len >= MSG_SIZ) && appData.debugMode )
900 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902 DisplayFatalError(buf, 0, 2);
906 cps->protocolVersion = appData.protocolVersion[n];
909 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
910 ParseFeatures(appData.featureDefaults, cps);
913 ChessProgramState *savCps;
921 if(WaitForEngine(savCps, LoadEngine)) return;
922 CommonEngineInit(); // recalculate time odds
923 if(gameInfo.variant != StringToVariant(appData.variant)) {
924 // we changed variant when loading the engine; this forces us to reset
925 Reset(TRUE, savCps != &first);
926 oldMode = BeginningOfGame; // to prevent restoring old mode
928 InitChessProgram(savCps, FALSE);
929 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930 DisplayMessage("", "");
931 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
935 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
939 ReplaceEngine (ChessProgramState *cps, int n)
941 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943 if(oldMode != BeginningOfGame) EditGameEvent();
946 appData.noChessProgram = FALSE;
947 appData.clockMode = TRUE;
950 if(n) return; // only startup first engine immediately; second can wait
951 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958 static char resetOptions[] =
959 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
965 FloatToFront(char **list, char *engineLine)
967 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969 if(appData.recentEngines <= 0) return;
970 TidyProgramName(engineLine, "localhost", tidy+1);
971 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972 strncpy(buf+1, *list, MSG_SIZ-50);
973 if(p = strstr(buf, tidy)) { // tidy name appears in list
974 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975 while(*p++ = *++q); // squeeze out
977 strcat(tidy, buf+1); // put list behind tidy name
978 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980 ASSIGN(*list, tidy+1);
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
986 Load (ChessProgramState *cps, int i)
988 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994 appData.firstProtocolVersion = PROTOVER;
995 ParseArgsFromString(buf);
997 ReplaceEngine(cps, i);
998 FloatToFront(&appData.recentEngineList, engineLine);
1002 while(q = strchr(p, SLASH)) p = q+1;
1003 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004 if(engineDir[0] != NULLCHAR) {
1005 ASSIGN(appData.directory[i], engineDir); p = engineName;
1006 } else if(p != engineName) { // derive directory from engine path, when not given
1008 ASSIGN(appData.directory[i], engineName);
1010 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011 } else { ASSIGN(appData.directory[i], "."); }
1012 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1014 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015 snprintf(command, MSG_SIZ, "%s %s", p, params);
1018 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019 ASSIGN(appData.chessProgram[i], p);
1020 appData.isUCI[i] = isUCI;
1021 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022 appData.hasOwnBookUCI[i] = hasBook;
1023 if(!nickName[0]) useNick = FALSE;
1024 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1028 q = firstChessProgramNames;
1029 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032 quote, p, quote, appData.directory[i],
1033 useNick ? " -fn \"" : "",
1034 useNick ? nickName : "",
1035 useNick ? "\"" : "",
1036 v1 ? " -firstProtocolVersion 1" : "",
1037 hasBook ? "" : " -fNoOwnBookUCI",
1038 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039 storeVariant ? " -variant " : "",
1040 storeVariant ? VariantName(gameInfo.variant) : "");
1041 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043 if(insert != q) insert[-1] = NULLCHAR;
1044 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1046 FloatToFront(&appData.recentEngineList, buf);
1048 ReplaceEngine(cps, i);
1054 int matched, min, sec;
1056 * Parse timeControl resource
1058 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059 appData.movesPerSession)) {
1061 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062 DisplayFatalError(buf, 0, 2);
1066 * Parse searchTime resource
1068 if (*appData.searchTime != NULLCHAR) {
1069 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1071 searchTime = min * 60;
1072 } else if (matched == 2) {
1073 searchTime = min * 60 + sec;
1076 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077 DisplayFatalError(buf, 0, 2);
1086 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1089 GetTimeMark(&programStartTime);
1090 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091 appData.seedBase = random() + (random()<<15);
1092 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1094 ClearProgramStats();
1095 programStats.ok_to_send = 1;
1096 programStats.seen_stat = 0;
1099 * Initialize game list
1105 * Internet chess server status
1107 if (appData.icsActive) {
1108 appData.matchMode = FALSE;
1109 appData.matchGames = 0;
1111 appData.noChessProgram = !appData.zippyPlay;
1113 appData.zippyPlay = FALSE;
1114 appData.zippyTalk = FALSE;
1115 appData.noChessProgram = TRUE;
1117 if (*appData.icsHelper != NULLCHAR) {
1118 appData.useTelnet = TRUE;
1119 appData.telnetProgram = appData.icsHelper;
1122 appData.zippyTalk = appData.zippyPlay = FALSE;
1125 /* [AS] Initialize pv info list [HGM] and game state */
1129 for( i=0; i<=framePtr; i++ ) {
1130 pvInfoList[i].depth = -1;
1131 boards[i][EP_STATUS] = EP_NONE;
1132 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1138 /* [AS] Adjudication threshold */
1139 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1141 InitEngine(&first, 0);
1142 InitEngine(&second, 1);
1145 pairing.which = "pairing"; // pairing engine
1146 pairing.pr = NoProc;
1148 pairing.program = appData.pairingEngine;
1149 pairing.host = "localhost";
1152 if (appData.icsActive) {
1153 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1154 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155 appData.clockMode = FALSE;
1156 first.sendTime = second.sendTime = 0;
1160 /* Override some settings from environment variables, for backward
1161 compatibility. Unfortunately it's not feasible to have the env
1162 vars just set defaults, at least in xboard. Ugh.
1164 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1169 if (!appData.icsActive) {
1173 /* Check for variants that are supported only in ICS mode,
1174 or not at all. Some that are accepted here nevertheless
1175 have bugs; see comments below.
1177 VariantClass variant = StringToVariant(appData.variant);
1179 case VariantBughouse: /* need four players and two boards */
1180 case VariantKriegspiel: /* need to hide pieces and move details */
1181 /* case VariantFischeRandom: (Fabien: moved below) */
1182 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183 if( (len >= MSG_SIZ) && appData.debugMode )
1184 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1186 DisplayFatalError(buf, 0, 2);
1189 case VariantUnknown:
1190 case VariantLoadable:
1200 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201 if( (len >= MSG_SIZ) && appData.debugMode )
1202 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1204 DisplayFatalError(buf, 0, 2);
1207 case VariantNormal: /* definitely works! */
1208 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1212 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1213 case VariantFairy: /* [HGM] TestLegality definitely off! */
1214 case VariantGothic: /* [HGM] should work */
1215 case VariantCapablanca: /* [HGM] should work */
1216 case VariantCourier: /* [HGM] initial forced moves not implemented */
1217 case VariantShogi: /* [HGM] could still mate with pawn drop */
1218 case VariantChu: /* [HGM] experimental */
1219 case VariantKnightmate: /* [HGM] should work */
1220 case VariantCylinder: /* [HGM] untested */
1221 case VariantFalcon: /* [HGM] untested */
1222 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223 offboard interposition not understood */
1224 case VariantWildCastle: /* pieces not automatically shuffled */
1225 case VariantNoCastle: /* pieces not automatically shuffled */
1226 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227 case VariantLosers: /* should work except for win condition,
1228 and doesn't know captures are mandatory */
1229 case VariantSuicide: /* should work except for win condition,
1230 and doesn't know captures are mandatory */
1231 case VariantGiveaway: /* should work except for win condition,
1232 and doesn't know captures are mandatory */
1233 case VariantTwoKings: /* should work */
1234 case VariantAtomic: /* should work except for win condition */
1235 case Variant3Check: /* should work except for win condition */
1236 case VariantShatranj: /* should work except for all win conditions */
1237 case VariantMakruk: /* should work except for draw countdown */
1238 case VariantASEAN : /* should work except for draw countdown */
1239 case VariantBerolina: /* might work if TestLegality is off */
1240 case VariantCapaRandom: /* should work */
1241 case VariantJanus: /* should work */
1242 case VariantSuper: /* experimental */
1243 case VariantGreat: /* experimental, requires legality testing to be off */
1244 case VariantSChess: /* S-Chess, should work */
1245 case VariantGrand: /* should work */
1246 case VariantSpartan: /* should work */
1247 case VariantLion: /* should work */
1248 case VariantChuChess: /* should work */
1256 NextIntegerFromString (char ** str, long * value)
1261 while( *s == ' ' || *s == '\t' ) {
1267 if( *s >= '0' && *s <= '9' ) {
1268 while( *s >= '0' && *s <= '9' ) {
1269 *value = *value * 10 + (*s - '0');
1282 NextTimeControlFromString (char ** str, long * value)
1285 int result = NextIntegerFromString( str, &temp );
1288 *value = temp * 60; /* Minutes */
1289 if( **str == ':' ) {
1291 result = NextIntegerFromString( str, &temp );
1292 *value += temp; /* Seconds */
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302 int result = -1, type = 0; long temp, temp2;
1304 if(**str != ':') return -1; // old params remain in force!
1306 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307 if( NextIntegerFromString( str, &temp ) ) return -1;
1308 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1311 /* time only: incremental or sudden-death time control */
1312 if(**str == '+') { /* increment follows; read it */
1314 if(**str == '!') type = *(*str)++; // Bronstein TC
1315 if(result = NextIntegerFromString( str, &temp2)) return -1;
1316 *inc = temp2 * 1000;
1317 if(**str == '.') { // read fraction of increment
1318 char *start = ++(*str);
1319 if(result = NextIntegerFromString( str, &temp2)) return -1;
1321 while(start++ < *str) temp2 /= 10;
1325 *moves = 0; *tc = temp * 1000; *incType = type;
1329 (*str)++; /* classical time control */
1330 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 { /* [HGM] get time to add from the multi-session time-control string */
1344 int incType, moves=1; /* kludge to force reading of first session */
1345 long time, increment;
1348 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1350 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352 if(movenr == -1) return time; /* last move before new session */
1353 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355 if(!moves) return increment; /* current session is incremental */
1356 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357 } while(movenr >= -1); /* try again for next session */
1359 return 0; // no new time quota on this move
1363 ParseTimeControl (char *tc, float ti, int mps)
1367 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1370 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1376 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1378 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1381 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1383 snprintf(buf, MSG_SIZ, ":%s", mytc);
1385 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1387 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1392 /* Parse second time control */
1395 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1403 timeControl_2 = tc2 * 1000;
1413 timeControl = tc1 * 1000;
1416 timeIncrement = ti * 1000; /* convert to ms */
1417 movesPerSession = 0;
1420 movesPerSession = mps;
1428 if (appData.debugMode) {
1429 # ifdef __GIT_VERSION
1430 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1432 fprintf(debugFP, "Version: %s\n", programVersion);
1435 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1437 set_cont_sequence(appData.wrapContSeq);
1438 if (appData.matchGames > 0) {
1439 appData.matchMode = TRUE;
1440 } else if (appData.matchMode) {
1441 appData.matchGames = 1;
1443 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444 appData.matchGames = appData.sameColorGames;
1445 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1450 if (appData.noChessProgram || first.protocolVersion == 1) {
1453 /* kludge: allow timeout for initial "feature" commands */
1455 DisplayMessage("", _("Starting chess program"));
1456 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1461 CalculateIndex (int index, int gameNr)
1462 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1464 if(index > 0) return index; // fixed nmber
1465 if(index == 0) return 1;
1466 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1472 LoadGameOrPosition (int gameNr)
1473 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474 if (*appData.loadGameFile != NULLCHAR) {
1475 if (!LoadGameFromFile(appData.loadGameFile,
1476 CalculateIndex(appData.loadGameIndex, gameNr),
1477 appData.loadGameFile, FALSE)) {
1478 DisplayFatalError(_("Bad game file"), 0, 1);
1481 } else if (*appData.loadPositionFile != NULLCHAR) {
1482 if (!LoadPositionFromFile(appData.loadPositionFile,
1483 CalculateIndex(appData.loadPositionIndex, gameNr),
1484 appData.loadPositionFile)) {
1485 DisplayFatalError(_("Bad position file"), 0, 1);
1493 ReserveGame (int gameNr, char resChar)
1495 FILE *tf = fopen(appData.tourneyFile, "r+");
1496 char *p, *q, c, buf[MSG_SIZ];
1497 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498 safeStrCpy(buf, lastMsg, MSG_SIZ);
1499 DisplayMessage(_("Pick new game"), "");
1500 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501 ParseArgsFromFile(tf);
1502 p = q = appData.results;
1503 if(appData.debugMode) {
1504 char *r = appData.participants;
1505 fprintf(debugFP, "results = '%s'\n", p);
1506 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507 fprintf(debugFP, "\n");
1509 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1511 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512 safeStrCpy(q, p, strlen(p) + 2);
1513 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1519 fseek(tf, -(strlen(p)+4), SEEK_END);
1521 if(c != '"') // depending on DOS or Unix line endings we can be one off
1522 fseek(tf, -(strlen(p)+2), SEEK_END);
1523 else fseek(tf, -(strlen(p)+3), SEEK_END);
1524 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525 DisplayMessage(buf, "");
1526 free(p); appData.results = q;
1527 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529 int round = appData.defaultMatchGames * appData.tourneyType;
1530 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1531 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532 UnloadEngine(&first); // next game belongs to other pairing;
1533 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1535 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1539 MatchEvent (int mode)
1540 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1542 if(matchMode) { // already in match mode: switch it off
1544 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1547 // if(gameMode != BeginningOfGame) {
1548 // DisplayError(_("You can only start a match from the initial position."), 0);
1552 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553 /* Set up machine vs. machine match */
1555 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556 if(appData.tourneyFile[0]) {
1558 if(nextGame > appData.matchGames) {
1560 if(strchr(appData.results, '*') == NULL) {
1562 appData.tourneyCycles++;
1563 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1565 NextTourneyGame(-1, &dummy);
1567 if(nextGame <= appData.matchGames) {
1568 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1570 ScheduleDelayedEvent(NextMatchGame, 10000);
1575 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576 DisplayError(buf, 0);
1577 appData.tourneyFile[0] = 0;
1581 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1582 DisplayFatalError(_("Can't have a match with no chess programs"),
1587 matchGame = roundNr = 1;
1588 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1595 InitBackEnd3 P((void))
1597 GameMode initialMode;
1601 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1602 !strcmp(appData.variant, "normal") && // no explicit variant request
1603 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1604 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1605 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606 char c, *q = first.variants, *p = strchr(q, ',');
1607 if(p) *p = NULLCHAR;
1608 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1610 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613 Reset(TRUE, FALSE); // and re-initialize
1618 InitChessProgram(&first, startedFromSetupPosition);
1620 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1621 free(programVersion);
1622 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1627 if (appData.icsActive) {
1629 /* [DM] Make a console window if needed [HGM] merged ifs */
1635 if (*appData.icsCommPort != NULLCHAR)
1636 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637 appData.icsCommPort);
1639 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640 appData.icsHost, appData.icsPort);
1642 if( (len >= MSG_SIZ) && appData.debugMode )
1643 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1645 DisplayFatalError(buf, err, 1);
1650 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1652 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655 } else if (appData.noChessProgram) {
1661 if (*appData.cmailGameName != NULLCHAR) {
1663 OpenLoopback(&cmailPR);
1665 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1669 DisplayMessage("", "");
1670 if (StrCaseCmp(appData.initialMode, "") == 0) {
1671 initialMode = BeginningOfGame;
1672 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1678 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679 initialMode = TwoMachinesPlay;
1680 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681 initialMode = AnalyzeFile;
1682 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683 initialMode = AnalyzeMode;
1684 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685 initialMode = MachinePlaysWhite;
1686 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687 initialMode = MachinePlaysBlack;
1688 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689 initialMode = EditGame;
1690 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691 initialMode = EditPosition;
1692 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693 initialMode = Training;
1695 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696 if( (len >= MSG_SIZ) && appData.debugMode )
1697 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1699 DisplayFatalError(buf, 0, 2);
1703 if (appData.matchMode) {
1704 if(appData.tourneyFile[0]) { // start tourney from command line
1706 if(f = fopen(appData.tourneyFile, "r")) {
1707 ParseArgsFromFile(f); // make sure tourney parmeters re known
1709 appData.clockMode = TRUE;
1711 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1714 } else if (*appData.cmailGameName != NULLCHAR) {
1715 /* Set up cmail mode */
1716 ReloadCmailMsgEvent(TRUE);
1718 /* Set up other modes */
1719 if (initialMode == AnalyzeFile) {
1720 if (*appData.loadGameFile == NULLCHAR) {
1721 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1725 if (*appData.loadGameFile != NULLCHAR) {
1726 (void) LoadGameFromFile(appData.loadGameFile,
1727 appData.loadGameIndex,
1728 appData.loadGameFile, TRUE);
1729 } else if (*appData.loadPositionFile != NULLCHAR) {
1730 (void) LoadPositionFromFile(appData.loadPositionFile,
1731 appData.loadPositionIndex,
1732 appData.loadPositionFile);
1733 /* [HGM] try to make self-starting even after FEN load */
1734 /* to allow automatic setup of fairy variants with wtm */
1735 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736 gameMode = BeginningOfGame;
1737 setboardSpoiledMachineBlack = 1;
1739 /* [HGM] loadPos: make that every new game uses the setup */
1740 /* from file as long as we do not switch variant */
1741 if(!blackPlaysFirst) {
1742 startedFromPositionFile = TRUE;
1743 CopyBoard(filePosition, boards[0]);
1744 CopyBoard(initialPosition, boards[0]);
1746 } else if(*appData.fen != NULLCHAR) {
1747 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1748 startedFromPositionFile = TRUE;
1752 if (initialMode == AnalyzeMode) {
1753 if (appData.noChessProgram) {
1754 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1757 if (appData.icsActive) {
1758 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1762 } else if (initialMode == AnalyzeFile) {
1763 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1764 ShowThinkingEvent();
1766 AnalysisPeriodicEvent(1);
1767 } else if (initialMode == MachinePlaysWhite) {
1768 if (appData.noChessProgram) {
1769 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1773 if (appData.icsActive) {
1774 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1778 MachineWhiteEvent();
1779 } else if (initialMode == MachinePlaysBlack) {
1780 if (appData.noChessProgram) {
1781 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1785 if (appData.icsActive) {
1786 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1790 MachineBlackEvent();
1791 } else if (initialMode == TwoMachinesPlay) {
1792 if (appData.noChessProgram) {
1793 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1797 if (appData.icsActive) {
1798 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1803 } else if (initialMode == EditGame) {
1805 } else if (initialMode == EditPosition) {
1806 EditPositionEvent();
1807 } else if (initialMode == Training) {
1808 if (*appData.loadGameFile == NULLCHAR) {
1809 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1818 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1820 DisplayBook(current+1);
1822 MoveHistorySet( movelist, first, last, current, pvInfoList );
1824 EvalGraphSet( first, last, current, pvInfoList );
1826 MakeEngineOutputTitle();
1830 * Establish will establish a contact to a remote host.port.
1831 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1832 * used to talk to the host.
1833 * Returns 0 if okay, error code if not.
1840 if (*appData.icsCommPort != NULLCHAR) {
1841 /* Talk to the host through a serial comm port */
1842 return OpenCommPort(appData.icsCommPort, &icsPR);
1844 } else if (*appData.gateway != NULLCHAR) {
1845 if (*appData.remoteShell == NULLCHAR) {
1846 /* Use the rcmd protocol to run telnet program on a gateway host */
1847 snprintf(buf, sizeof(buf), "%s %s %s",
1848 appData.telnetProgram, appData.icsHost, appData.icsPort);
1849 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1852 /* Use the rsh program to run telnet program on a gateway host */
1853 if (*appData.remoteUser == NULLCHAR) {
1854 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1855 appData.gateway, appData.telnetProgram,
1856 appData.icsHost, appData.icsPort);
1858 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1859 appData.remoteShell, appData.gateway,
1860 appData.remoteUser, appData.telnetProgram,
1861 appData.icsHost, appData.icsPort);
1863 return StartChildProcess(buf, "", &icsPR);
1866 } else if (appData.useTelnet) {
1867 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1870 /* TCP socket interface differs somewhat between
1871 Unix and NT; handle details in the front end.
1873 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1878 EscapeExpand (char *p, char *q)
1879 { // [HGM] initstring: routine to shape up string arguments
1880 while(*p++ = *q++) if(p[-1] == '\\')
1882 case 'n': p[-1] = '\n'; break;
1883 case 'r': p[-1] = '\r'; break;
1884 case 't': p[-1] = '\t'; break;
1885 case '\\': p[-1] = '\\'; break;
1886 case 0: *p = 0; return;
1887 default: p[-1] = q[-1]; break;
1892 show_bytes (FILE *fp, char *buf, int count)
1895 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1896 fprintf(fp, "\\%03o", *buf & 0xff);
1905 /* Returns an errno value */
1907 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1909 char buf[8192], *p, *q, *buflim;
1910 int left, newcount, outcount;
1912 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1913 *appData.gateway != NULLCHAR) {
1914 if (appData.debugMode) {
1915 fprintf(debugFP, ">ICS: ");
1916 show_bytes(debugFP, message, count);
1917 fprintf(debugFP, "\n");
1919 return OutputToProcess(pr, message, count, outError);
1922 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1929 if (appData.debugMode) {
1930 fprintf(debugFP, ">ICS: ");
1931 show_bytes(debugFP, buf, newcount);
1932 fprintf(debugFP, "\n");
1934 outcount = OutputToProcess(pr, buf, newcount, outError);
1935 if (outcount < newcount) return -1; /* to be sure */
1942 } else if (((unsigned char) *p) == TN_IAC) {
1943 *q++ = (char) TN_IAC;
1950 if (appData.debugMode) {
1951 fprintf(debugFP, ">ICS: ");
1952 show_bytes(debugFP, buf, newcount);
1953 fprintf(debugFP, "\n");
1955 outcount = OutputToProcess(pr, buf, newcount, outError);
1956 if (outcount < newcount) return -1; /* to be sure */
1961 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1963 int outError, outCount;
1964 static int gotEof = 0;
1967 /* Pass data read from player on to ICS */
1970 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1971 if (outCount < count) {
1972 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1974 if(have_sent_ICS_logon == 2) {
1975 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1976 fprintf(ini, "%s", message);
1977 have_sent_ICS_logon = 3;
1979 have_sent_ICS_logon = 1;
1980 } else if(have_sent_ICS_logon == 3) {
1981 fprintf(ini, "%s", message);
1983 have_sent_ICS_logon = 1;
1985 } else if (count < 0) {
1986 RemoveInputSource(isr);
1987 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1988 } else if (gotEof++ > 0) {
1989 RemoveInputSource(isr);
1990 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1996 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1997 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1998 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1999 SendToICS("date\n");
2000 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2003 /* added routine for printf style output to ics */
2005 ics_printf (char *format, ...)
2007 char buffer[MSG_SIZ];
2010 va_start(args, format);
2011 vsnprintf(buffer, sizeof(buffer), format, args);
2012 buffer[sizeof(buffer)-1] = '\0';
2020 int count, outCount, outError;
2022 if (icsPR == NoProc) return;
2025 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2026 if (outCount < count) {
2027 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031 /* This is used for sending logon scripts to the ICS. Sending
2032 without a delay causes problems when using timestamp on ICC
2033 (at least on my machine). */
2035 SendToICSDelayed (char *s, long msdelay)
2037 int count, outCount, outError;
2039 if (icsPR == NoProc) return;
2042 if (appData.debugMode) {
2043 fprintf(debugFP, ">ICS: ");
2044 show_bytes(debugFP, s, count);
2045 fprintf(debugFP, "\n");
2047 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2049 if (outCount < count) {
2050 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2055 /* Remove all highlighting escape sequences in s
2056 Also deletes any suffix starting with '('
2059 StripHighlightAndTitle (char *s)
2061 static char retbuf[MSG_SIZ];
2064 while (*s != NULLCHAR) {
2065 while (*s == '\033') {
2066 while (*s != NULLCHAR && !isalpha(*s)) s++;
2067 if (*s != NULLCHAR) s++;
2069 while (*s != NULLCHAR && *s != '\033') {
2070 if (*s == '(' || *s == '[') {
2081 /* Remove all highlighting escape sequences in s */
2083 StripHighlight (char *s)
2085 static char retbuf[MSG_SIZ];
2088 while (*s != NULLCHAR) {
2089 while (*s == '\033') {
2090 while (*s != NULLCHAR && !isalpha(*s)) s++;
2091 if (*s != NULLCHAR) s++;
2093 while (*s != NULLCHAR && *s != '\033') {
2101 char engineVariant[MSG_SIZ];
2102 char *variantNames[] = VARIANT_NAMES;
2104 VariantName (VariantClass v)
2106 if(v == VariantUnknown || *engineVariant) return engineVariant;
2107 return variantNames[v];
2111 /* Identify a variant from the strings the chess servers use or the
2112 PGN Variant tag names we use. */
2114 StringToVariant (char *e)
2118 VariantClass v = VariantNormal;
2119 int i, found = FALSE;
2120 char buf[MSG_SIZ], c;
2125 /* [HGM] skip over optional board-size prefixes */
2126 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2127 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2128 while( *e++ != '_');
2131 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2135 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2136 if (p = StrCaseStr(e, variantNames[i])) {
2137 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2138 v = (VariantClass) i;
2145 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2146 || StrCaseStr(e, "wild/fr")
2147 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2148 v = VariantFischeRandom;
2149 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2150 (i = 1, p = StrCaseStr(e, "w"))) {
2152 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2159 case 0: /* FICS only, actually */
2161 /* Castling legal even if K starts on d-file */
2162 v = VariantWildCastle;
2167 /* Castling illegal even if K & R happen to start in
2168 normal positions. */
2169 v = VariantNoCastle;
2182 /* Castling legal iff K & R start in normal positions */
2188 /* Special wilds for position setup; unclear what to do here */
2189 v = VariantLoadable;
2192 /* Bizarre ICC game */
2193 v = VariantTwoKings;
2196 v = VariantKriegspiel;
2202 v = VariantFischeRandom;
2205 v = VariantCrazyhouse;
2208 v = VariantBughouse;
2214 /* Not quite the same as FICS suicide! */
2215 v = VariantGiveaway;
2221 v = VariantShatranj;
2224 /* Temporary names for future ICC types. The name *will* change in
2225 the next xboard/WinBoard release after ICC defines it. */
2263 v = VariantCapablanca;
2266 v = VariantKnightmate;
2272 v = VariantCylinder;
2278 v = VariantCapaRandom;
2281 v = VariantBerolina;
2293 /* Found "wild" or "w" in the string but no number;
2294 must assume it's normal chess. */
2298 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2299 if( (len >= MSG_SIZ) && appData.debugMode )
2300 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2302 DisplayError(buf, 0);
2308 if (appData.debugMode) {
2309 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2310 e, wnum, VariantName(v));
2315 static int leftover_start = 0, leftover_len = 0;
2316 char star_match[STAR_MATCH_N][MSG_SIZ];
2318 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2319 advance *index beyond it, and set leftover_start to the new value of
2320 *index; else return FALSE. If pattern contains the character '*', it
2321 matches any sequence of characters not containing '\r', '\n', or the
2322 character following the '*' (if any), and the matched sequence(s) are
2323 copied into star_match.
2326 looking_at ( char *buf, int *index, char *pattern)
2328 char *bufp = &buf[*index], *patternp = pattern;
2330 char *matchp = star_match[0];
2333 if (*patternp == NULLCHAR) {
2334 *index = leftover_start = bufp - buf;
2338 if (*bufp == NULLCHAR) return FALSE;
2339 if (*patternp == '*') {
2340 if (*bufp == *(patternp + 1)) {
2342 matchp = star_match[++star_count];
2346 } else if (*bufp == '\n' || *bufp == '\r') {
2348 if (*patternp == NULLCHAR)
2353 *matchp++ = *bufp++;
2357 if (*patternp != *bufp) return FALSE;
2364 SendToPlayer (char *data, int length)
2366 int error, outCount;
2367 outCount = OutputToProcess(NoProc, data, length, &error);
2368 if (outCount < length) {
2369 DisplayFatalError(_("Error writing to display"), error, 1);
2374 PackHolding (char packed[], char *holding)
2384 switch (runlength) {
2395 sprintf(q, "%d", runlength);
2407 /* Telnet protocol requests from the front end */
2409 TelnetRequest (unsigned char ddww, unsigned char option)
2411 unsigned char msg[3];
2412 int outCount, outError;
2414 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2416 if (appData.debugMode) {
2417 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2433 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2442 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2445 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2450 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2452 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2459 if (!appData.icsActive) return;
2460 TelnetRequest(TN_DO, TN_ECHO);
2466 if (!appData.icsActive) return;
2467 TelnetRequest(TN_DONT, TN_ECHO);
2471 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2473 /* put the holdings sent to us by the server on the board holdings area */
2474 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2478 if(gameInfo.holdingsWidth < 2) return;
2479 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2480 return; // prevent overwriting by pre-board holdings
2482 if( (int)lowestPiece >= BlackPawn ) {
2485 holdingsStartRow = BOARD_HEIGHT-1;
2488 holdingsColumn = BOARD_WIDTH-1;
2489 countsColumn = BOARD_WIDTH-2;
2490 holdingsStartRow = 0;
2494 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2495 board[i][holdingsColumn] = EmptySquare;
2496 board[i][countsColumn] = (ChessSquare) 0;
2498 while( (p=*holdings++) != NULLCHAR ) {
2499 piece = CharToPiece( ToUpper(p) );
2500 if(piece == EmptySquare) continue;
2501 /*j = (int) piece - (int) WhitePawn;*/
2502 j = PieceToNumber(piece);
2503 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2504 if(j < 0) continue; /* should not happen */
2505 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2506 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2507 board[holdingsStartRow+j*direction][countsColumn]++;
2513 VariantSwitch (Board board, VariantClass newVariant)
2515 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2516 static Board oldBoard;
2518 startedFromPositionFile = FALSE;
2519 if(gameInfo.variant == newVariant) return;
2521 /* [HGM] This routine is called each time an assignment is made to
2522 * gameInfo.variant during a game, to make sure the board sizes
2523 * are set to match the new variant. If that means adding or deleting
2524 * holdings, we shift the playing board accordingly
2525 * This kludge is needed because in ICS observe mode, we get boards
2526 * of an ongoing game without knowing the variant, and learn about the
2527 * latter only later. This can be because of the move list we requested,
2528 * in which case the game history is refilled from the beginning anyway,
2529 * but also when receiving holdings of a crazyhouse game. In the latter
2530 * case we want to add those holdings to the already received position.
2534 if (appData.debugMode) {
2535 fprintf(debugFP, "Switch board from %s to %s\n",
2536 VariantName(gameInfo.variant), VariantName(newVariant));
2537 setbuf(debugFP, NULL);
2539 shuffleOpenings = 0; /* [HGM] shuffle */
2540 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2544 newWidth = 9; newHeight = 9;
2545 gameInfo.holdingsSize = 7;
2546 case VariantBughouse:
2547 case VariantCrazyhouse:
2548 newHoldingsWidth = 2; break;
2552 newHoldingsWidth = 2;
2553 gameInfo.holdingsSize = 8;
2556 case VariantCapablanca:
2557 case VariantCapaRandom:
2560 newHoldingsWidth = gameInfo.holdingsSize = 0;
2563 if(newWidth != gameInfo.boardWidth ||
2564 newHeight != gameInfo.boardHeight ||
2565 newHoldingsWidth != gameInfo.holdingsWidth ) {
2567 /* shift position to new playing area, if needed */
2568 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2569 for(i=0; i<BOARD_HEIGHT; i++)
2570 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2571 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2573 for(i=0; i<newHeight; i++) {
2574 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2575 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2577 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2578 for(i=0; i<BOARD_HEIGHT; i++)
2579 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2580 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2583 board[HOLDINGS_SET] = 0;
2584 gameInfo.boardWidth = newWidth;
2585 gameInfo.boardHeight = newHeight;
2586 gameInfo.holdingsWidth = newHoldingsWidth;
2587 gameInfo.variant = newVariant;
2588 InitDrawingSizes(-2, 0);
2589 } else gameInfo.variant = newVariant;
2590 CopyBoard(oldBoard, board); // remember correctly formatted board
2591 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2592 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2595 static int loggedOn = FALSE;
2597 /*-- Game start info cache: --*/
2599 char gs_kind[MSG_SIZ];
2600 static char player1Name[128] = "";
2601 static char player2Name[128] = "";
2602 static char cont_seq[] = "\n\\ ";
2603 static int player1Rating = -1;
2604 static int player2Rating = -1;
2605 /*----------------------------*/
2607 ColorClass curColor = ColorNormal;
2608 int suppressKibitz = 0;
2611 Boolean soughtPending = FALSE;
2612 Boolean seekGraphUp;
2613 #define MAX_SEEK_ADS 200
2615 char *seekAdList[MAX_SEEK_ADS];
2616 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2617 float tcList[MAX_SEEK_ADS];
2618 char colorList[MAX_SEEK_ADS];
2619 int nrOfSeekAds = 0;
2620 int minRating = 1010, maxRating = 2800;
2621 int hMargin = 10, vMargin = 20, h, w;
2622 extern int squareSize, lineGap;
2627 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2628 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2629 if(r < minRating+100 && r >=0 ) r = minRating+100;
2630 if(r > maxRating) r = maxRating;
2631 if(tc < 1.f) tc = 1.f;
2632 if(tc > 95.f) tc = 95.f;
2633 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2634 y = ((double)r - minRating)/(maxRating - minRating)
2635 * (h-vMargin-squareSize/8-1) + vMargin;
2636 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2637 if(strstr(seekAdList[i], " u ")) color = 1;
2638 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2639 !strstr(seekAdList[i], "bullet") &&
2640 !strstr(seekAdList[i], "blitz") &&
2641 !strstr(seekAdList[i], "standard") ) color = 2;
2642 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2643 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2647 PlotSingleSeekAd (int i)
2653 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2655 char buf[MSG_SIZ], *ext = "";
2656 VariantClass v = StringToVariant(type);
2657 if(strstr(type, "wild")) {
2658 ext = type + 4; // append wild number
2659 if(v == VariantFischeRandom) type = "chess960"; else
2660 if(v == VariantLoadable) type = "setup"; else
2661 type = VariantName(v);
2663 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2664 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2665 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2666 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2667 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2668 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2669 seekNrList[nrOfSeekAds] = nr;
2670 zList[nrOfSeekAds] = 0;
2671 seekAdList[nrOfSeekAds++] = StrSave(buf);
2672 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2677 EraseSeekDot (int i)
2679 int x = xList[i], y = yList[i], d=squareSize/4, k;
2680 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2681 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2682 // now replot every dot that overlapped
2683 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2684 int xx = xList[k], yy = yList[k];
2685 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2686 DrawSeekDot(xx, yy, colorList[k]);
2691 RemoveSeekAd (int nr)
2694 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2696 if(seekAdList[i]) free(seekAdList[i]);
2697 seekAdList[i] = seekAdList[--nrOfSeekAds];
2698 seekNrList[i] = seekNrList[nrOfSeekAds];
2699 ratingList[i] = ratingList[nrOfSeekAds];
2700 colorList[i] = colorList[nrOfSeekAds];
2701 tcList[i] = tcList[nrOfSeekAds];
2702 xList[i] = xList[nrOfSeekAds];
2703 yList[i] = yList[nrOfSeekAds];
2704 zList[i] = zList[nrOfSeekAds];
2705 seekAdList[nrOfSeekAds] = NULL;
2711 MatchSoughtLine (char *line)
2713 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2714 int nr, base, inc, u=0; char dummy;
2716 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2717 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2719 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2721 // match: compact and save the line
2722 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2732 if(!seekGraphUp) return FALSE;
2733 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2734 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2736 DrawSeekBackground(0, 0, w, h);
2737 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2738 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2739 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2740 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2742 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2745 snprintf(buf, MSG_SIZ, "%d", i);
2746 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2749 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2750 for(i=1; i<100; i+=(i<10?1:5)) {
2751 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2752 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2753 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2755 snprintf(buf, MSG_SIZ, "%d", i);
2756 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2759 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2764 SeekGraphClick (ClickType click, int x, int y, int moving)
2766 static int lastDown = 0, displayed = 0, lastSecond;
2767 if(y < 0) return FALSE;
2768 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2769 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2770 if(!seekGraphUp) return FALSE;
2771 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2772 DrawPosition(TRUE, NULL);
2775 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2776 if(click == Release || moving) return FALSE;
2778 soughtPending = TRUE;
2779 SendToICS(ics_prefix);
2780 SendToICS("sought\n"); // should this be "sought all"?
2781 } else { // issue challenge based on clicked ad
2782 int dist = 10000; int i, closest = 0, second = 0;
2783 for(i=0; i<nrOfSeekAds; i++) {
2784 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2785 if(d < dist) { dist = d; closest = i; }
2786 second += (d - zList[i] < 120); // count in-range ads
2787 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2791 second = (second > 1);
2792 if(displayed != closest || second != lastSecond) {
2793 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2794 lastSecond = second; displayed = closest;
2796 if(click == Press) {
2797 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2800 } // on press 'hit', only show info
2801 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2802 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2803 SendToICS(ics_prefix);
2805 return TRUE; // let incoming board of started game pop down the graph
2806 } else if(click == Release) { // release 'miss' is ignored
2807 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2808 if(moving == 2) { // right up-click
2809 nrOfSeekAds = 0; // refresh graph
2810 soughtPending = TRUE;
2811 SendToICS(ics_prefix);
2812 SendToICS("sought\n"); // should this be "sought all"?
2815 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2816 // press miss or release hit 'pop down' seek graph
2817 seekGraphUp = FALSE;
2818 DrawPosition(TRUE, NULL);
2824 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2826 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2827 #define STARTED_NONE 0
2828 #define STARTED_MOVES 1
2829 #define STARTED_BOARD 2
2830 #define STARTED_OBSERVE 3
2831 #define STARTED_HOLDINGS 4
2832 #define STARTED_CHATTER 5
2833 #define STARTED_COMMENT 6
2834 #define STARTED_MOVES_NOHIDE 7
2836 static int started = STARTED_NONE;
2837 static char parse[20000];
2838 static int parse_pos = 0;
2839 static char buf[BUF_SIZE + 1];
2840 static int firstTime = TRUE, intfSet = FALSE;
2841 static ColorClass prevColor = ColorNormal;
2842 static int savingComment = FALSE;
2843 static int cmatch = 0; // continuation sequence match
2850 int backup; /* [DM] For zippy color lines */
2852 char talker[MSG_SIZ]; // [HGM] chat
2853 int channel, collective=0;
2855 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2857 if (appData.debugMode) {
2859 fprintf(debugFP, "<ICS: ");
2860 show_bytes(debugFP, data, count);
2861 fprintf(debugFP, "\n");
2865 if (appData.debugMode) { int f = forwardMostMove;
2866 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2867 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2868 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2871 /* If last read ended with a partial line that we couldn't parse,
2872 prepend it to the new read and try again. */
2873 if (leftover_len > 0) {
2874 for (i=0; i<leftover_len; i++)
2875 buf[i] = buf[leftover_start + i];
2878 /* copy new characters into the buffer */
2879 bp = buf + leftover_len;
2880 buf_len=leftover_len;
2881 for (i=0; i<count; i++)
2884 if (data[i] == '\r')
2887 // join lines split by ICS?
2888 if (!appData.noJoin)
2891 Joining just consists of finding matches against the
2892 continuation sequence, and discarding that sequence
2893 if found instead of copying it. So, until a match
2894 fails, there's nothing to do since it might be the
2895 complete sequence, and thus, something we don't want
2898 if (data[i] == cont_seq[cmatch])
2901 if (cmatch == strlen(cont_seq))
2903 cmatch = 0; // complete match. just reset the counter
2906 it's possible for the ICS to not include the space
2907 at the end of the last word, making our [correct]
2908 join operation fuse two separate words. the server
2909 does this when the space occurs at the width setting.
2911 if (!buf_len || buf[buf_len-1] != ' ')
2922 match failed, so we have to copy what matched before
2923 falling through and copying this character. In reality,
2924 this will only ever be just the newline character, but
2925 it doesn't hurt to be precise.
2927 strncpy(bp, cont_seq, cmatch);
2939 buf[buf_len] = NULLCHAR;
2940 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2945 while (i < buf_len) {
2946 /* Deal with part of the TELNET option negotiation
2947 protocol. We refuse to do anything beyond the
2948 defaults, except that we allow the WILL ECHO option,
2949 which ICS uses to turn off password echoing when we are
2950 directly connected to it. We reject this option
2951 if localLineEditing mode is on (always on in xboard)
2952 and we are talking to port 23, which might be a real
2953 telnet server that will try to keep WILL ECHO on permanently.
2955 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2956 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2957 unsigned char option;
2959 switch ((unsigned char) buf[++i]) {
2961 if (appData.debugMode)
2962 fprintf(debugFP, "\n<WILL ");
2963 switch (option = (unsigned char) buf[++i]) {
2965 if (appData.debugMode)
2966 fprintf(debugFP, "ECHO ");
2967 /* Reply only if this is a change, according
2968 to the protocol rules. */
2969 if (remoteEchoOption) break;
2970 if (appData.localLineEditing &&
2971 atoi(appData.icsPort) == TN_PORT) {
2972 TelnetRequest(TN_DONT, TN_ECHO);
2975 TelnetRequest(TN_DO, TN_ECHO);
2976 remoteEchoOption = TRUE;
2980 if (appData.debugMode)
2981 fprintf(debugFP, "%d ", option);
2982 /* Whatever this is, we don't want it. */
2983 TelnetRequest(TN_DONT, option);
2988 if (appData.debugMode)
2989 fprintf(debugFP, "\n<WONT ");
2990 switch (option = (unsigned char) buf[++i]) {
2992 if (appData.debugMode)
2993 fprintf(debugFP, "ECHO ");
2994 /* Reply only if this is a change, according
2995 to the protocol rules. */
2996 if (!remoteEchoOption) break;
2998 TelnetRequest(TN_DONT, TN_ECHO);
2999 remoteEchoOption = FALSE;
3002 if (appData.debugMode)
3003 fprintf(debugFP, "%d ", (unsigned char) option);
3004 /* Whatever this is, it must already be turned
3005 off, because we never agree to turn on
3006 anything non-default, so according to the
3007 protocol rules, we don't reply. */
3012 if (appData.debugMode)
3013 fprintf(debugFP, "\n<DO ");
3014 switch (option = (unsigned char) buf[++i]) {
3016 /* Whatever this is, we refuse to do it. */
3017 if (appData.debugMode)
3018 fprintf(debugFP, "%d ", option);
3019 TelnetRequest(TN_WONT, option);
3024 if (appData.debugMode)
3025 fprintf(debugFP, "\n<DONT ");
3026 switch (option = (unsigned char) buf[++i]) {
3028 if (appData.debugMode)
3029 fprintf(debugFP, "%d ", option);
3030 /* Whatever this is, we are already not doing
3031 it, because we never agree to do anything
3032 non-default, so according to the protocol
3033 rules, we don't reply. */
3038 if (appData.debugMode)
3039 fprintf(debugFP, "\n<IAC ");
3040 /* Doubled IAC; pass it through */
3044 if (appData.debugMode)
3045 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3046 /* Drop all other telnet commands on the floor */
3049 if (oldi > next_out)
3050 SendToPlayer(&buf[next_out], oldi - next_out);
3056 /* OK, this at least will *usually* work */
3057 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3061 if (loggedOn && !intfSet) {
3062 if (ics_type == ICS_ICC) {
3063 snprintf(str, MSG_SIZ,
3064 "/set-quietly interface %s\n/set-quietly style 12\n",
3066 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3067 strcat(str, "/set-2 51 1\n/set seek 1\n");
3068 } else if (ics_type == ICS_CHESSNET) {
3069 snprintf(str, MSG_SIZ, "/style 12\n");
3071 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3072 strcat(str, programVersion);
3073 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3074 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3075 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3077 strcat(str, "$iset nohighlight 1\n");
3079 strcat(str, "$iset lock 1\n$style 12\n");
3082 NotifyFrontendLogin();
3086 if (started == STARTED_COMMENT) {
3087 /* Accumulate characters in comment */
3088 parse[parse_pos++] = buf[i];
3089 if (buf[i] == '\n') {
3090 parse[parse_pos] = NULLCHAR;
3091 if(chattingPartner>=0) {
3093 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3094 OutputChatMessage(chattingPartner, mess);
3095 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3097 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3098 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3099 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3100 OutputChatMessage(p, mess);
3104 chattingPartner = -1;
3105 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3108 if(!suppressKibitz) // [HGM] kibitz
3109 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3110 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3111 int nrDigit = 0, nrAlph = 0, j;
3112 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3113 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3114 parse[parse_pos] = NULLCHAR;
3115 // try to be smart: if it does not look like search info, it should go to
3116 // ICS interaction window after all, not to engine-output window.
3117 for(j=0; j<parse_pos; j++) { // count letters and digits
3118 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3119 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3120 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3122 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3123 int depth=0; float score;
3124 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3125 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3126 pvInfoList[forwardMostMove-1].depth = depth;
3127 pvInfoList[forwardMostMove-1].score = 100*score;
3129 OutputKibitz(suppressKibitz, parse);
3132 if(gameMode == IcsObserving) // restore original ICS messages
3133 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3134 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3136 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3138 SendToPlayer(tmp, strlen(tmp));
3140 next_out = i+1; // [HGM] suppress printing in ICS window
3142 started = STARTED_NONE;
3144 /* Don't match patterns against characters in comment */
3149 if (started == STARTED_CHATTER) {
3150 if (buf[i] != '\n') {
3151 /* Don't match patterns against characters in chatter */
3155 started = STARTED_NONE;
3156 if(suppressKibitz) next_out = i+1;
3159 /* Kludge to deal with rcmd protocol */
3160 if (firstTime && looking_at(buf, &i, "\001*")) {
3161 DisplayFatalError(&buf[1], 0, 1);
3167 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3170 if (appData.debugMode)
3171 fprintf(debugFP, "ics_type %d\n", ics_type);
3174 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3175 ics_type = ICS_FICS;
3177 if (appData.debugMode)
3178 fprintf(debugFP, "ics_type %d\n", ics_type);
3181 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3182 ics_type = ICS_CHESSNET;
3184 if (appData.debugMode)
3185 fprintf(debugFP, "ics_type %d\n", ics_type);
3190 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3191 looking_at(buf, &i, "Logging you in as \"*\"") ||
3192 looking_at(buf, &i, "will be \"*\""))) {
3193 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3197 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3199 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3200 DisplayIcsInteractionTitle(buf);
3201 have_set_title = TRUE;
3204 /* skip finger notes */
3205 if (started == STARTED_NONE &&
3206 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3207 (buf[i] == '1' && buf[i+1] == '0')) &&
3208 buf[i+2] == ':' && buf[i+3] == ' ') {
3209 started = STARTED_CHATTER;
3215 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3216 if(appData.seekGraph) {
3217 if(soughtPending && MatchSoughtLine(buf+i)) {
3218 i = strstr(buf+i, "rated") - buf;
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 next_out = leftover_start = i;
3221 started = STARTED_CHATTER;
3222 suppressKibitz = TRUE;
3225 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3226 && looking_at(buf, &i, "* ads displayed")) {
3227 soughtPending = FALSE;
3232 if(appData.autoRefresh) {
3233 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3234 int s = (ics_type == ICS_ICC); // ICC format differs
3236 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3237 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3238 looking_at(buf, &i, "*% "); // eat prompt
3239 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3240 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3241 next_out = i; // suppress
3244 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3245 char *p = star_match[0];
3247 if(seekGraphUp) RemoveSeekAd(atoi(p));
3248 while(*p && *p++ != ' '); // next
3250 looking_at(buf, &i, "*% "); // eat prompt
3251 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3258 /* skip formula vars */
3259 if (started == STARTED_NONE &&
3260 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3261 started = STARTED_CHATTER;
3266 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3267 if (appData.autoKibitz && started == STARTED_NONE &&
3268 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3269 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3270 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3271 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3272 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3273 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3274 suppressKibitz = TRUE;
3275 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3278 && (gameMode == IcsPlayingWhite)) ||
3279 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3280 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3281 started = STARTED_CHATTER; // own kibitz we simply discard
3283 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3284 parse_pos = 0; parse[0] = NULLCHAR;
3285 savingComment = TRUE;
3286 suppressKibitz = gameMode != IcsObserving ? 2 :
3287 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3291 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3292 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3293 && atoi(star_match[0])) {
3294 // suppress the acknowledgements of our own autoKibitz
3296 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3297 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3298 SendToPlayer(star_match[0], strlen(star_match[0]));
3299 if(looking_at(buf, &i, "*% ")) // eat prompt
3300 suppressKibitz = FALSE;
3304 } // [HGM] kibitz: end of patch
3306 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3308 // [HGM] chat: intercept tells by users for which we have an open chat window
3310 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3311 looking_at(buf, &i, "* whispers:") ||
3312 looking_at(buf, &i, "* kibitzes:") ||
3313 looking_at(buf, &i, "* shouts:") ||
3314 looking_at(buf, &i, "* c-shouts:") ||
3315 looking_at(buf, &i, "--> * ") ||
3316 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3317 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3318 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3319 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3321 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3322 chattingPartner = -1; collective = 0;
3324 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3325 for(p=0; p<MAX_CHAT; p++) {
3327 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3328 talker[0] = '['; strcat(talker, "] ");
3329 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3330 chattingPartner = p; break;
3333 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3334 for(p=0; p<MAX_CHAT; p++) {
3336 if(!strcmp("kibitzes", chatPartner[p])) {
3337 talker[0] = '['; strcat(talker, "] ");
3338 chattingPartner = p; break;
3341 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3342 for(p=0; p<MAX_CHAT; p++) {
3344 if(!strcmp("whispers", chatPartner[p])) {
3345 talker[0] = '['; strcat(talker, "] ");
3346 chattingPartner = p; break;
3349 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3350 if(buf[i-8] == '-' && buf[i-3] == 't')
3351 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3353 if(!strcmp("c-shouts", chatPartner[p])) {
3354 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3355 chattingPartner = p; break;
3358 if(chattingPartner < 0)
3359 for(p=0; p<MAX_CHAT; p++) {
3361 if(!strcmp("shouts", chatPartner[p])) {
3362 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3363 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3364 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3365 chattingPartner = p; break;
3369 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3370 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3372 Colorize(ColorTell, FALSE);
3373 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3375 chattingPartner = p; break;
3377 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3378 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3379 started = STARTED_COMMENT;
3380 parse_pos = 0; parse[0] = NULLCHAR;
3381 savingComment = 3 + chattingPartner; // counts as TRUE
3382 if(collective == 3) i = oldi; else {
3383 suppressKibitz = TRUE;
3384 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3385 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3389 } // [HGM] chat: end of patch
3392 if (appData.zippyTalk || appData.zippyPlay) {
3393 /* [DM] Backup address for color zippy lines */
3395 if (loggedOn == TRUE)
3396 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3397 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3399 } // [DM] 'else { ' deleted
3401 /* Regular tells and says */
3402 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3403 looking_at(buf, &i, "* (your partner) tells you: ") ||
3404 looking_at(buf, &i, "* says: ") ||
3405 /* Don't color "message" or "messages" output */
3406 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3407 looking_at(buf, &i, "*. * at *:*: ") ||
3408 looking_at(buf, &i, "--* (*:*): ") ||
3409 /* Message notifications (same color as tells) */
3410 looking_at(buf, &i, "* has left a message ") ||
3411 looking_at(buf, &i, "* just sent you a message:\n") ||
3412 /* Whispers and kibitzes */
3413 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3414 looking_at(buf, &i, "* kibitzes: ") ||
3416 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3418 if (tkind == 1 && strchr(star_match[0], ':')) {
3419 /* Avoid "tells you:" spoofs in channels */
3422 if (star_match[0][0] == NULLCHAR ||
3423 strchr(star_match[0], ' ') ||
3424 (tkind == 3 && strchr(star_match[1], ' '))) {
3425 /* Reject bogus matches */
3428 if (appData.colorize) {
3429 if (oldi > next_out) {
3430 SendToPlayer(&buf[next_out], oldi - next_out);
3435 Colorize(ColorTell, FALSE);
3436 curColor = ColorTell;
3439 Colorize(ColorKibitz, FALSE);
3440 curColor = ColorKibitz;
3443 p = strrchr(star_match[1], '(');
3450 Colorize(ColorChannel1, FALSE);
3451 curColor = ColorChannel1;
3453 Colorize(ColorChannel, FALSE);
3454 curColor = ColorChannel;
3458 curColor = ColorNormal;
3462 if (started == STARTED_NONE && appData.autoComment &&
3463 (gameMode == IcsObserving ||
3464 gameMode == IcsPlayingWhite ||
3465 gameMode == IcsPlayingBlack)) {
3466 parse_pos = i - oldi;
3467 memcpy(parse, &buf[oldi], parse_pos);
3468 parse[parse_pos] = NULLCHAR;
3469 started = STARTED_COMMENT;
3470 savingComment = TRUE;
3471 } else if(collective != 3) {
3472 started = STARTED_CHATTER;
3473 savingComment = FALSE;
3480 if (looking_at(buf, &i, "* s-shouts: ") ||
3481 looking_at(buf, &i, "* c-shouts: ")) {
3482 if (appData.colorize) {
3483 if (oldi > next_out) {
3484 SendToPlayer(&buf[next_out], oldi - next_out);
3487 Colorize(ColorSShout, FALSE);
3488 curColor = ColorSShout;
3491 started = STARTED_CHATTER;
3495 if (looking_at(buf, &i, "--->")) {
3500 if (looking_at(buf, &i, "* shouts: ") ||
3501 looking_at(buf, &i, "--> ")) {
3502 if (appData.colorize) {
3503 if (oldi > next_out) {
3504 SendToPlayer(&buf[next_out], oldi - next_out);
3507 Colorize(ColorShout, FALSE);
3508 curColor = ColorShout;
3511 started = STARTED_CHATTER;
3515 if (looking_at( buf, &i, "Challenge:")) {
3516 if (appData.colorize) {
3517 if (oldi > next_out) {
3518 SendToPlayer(&buf[next_out], oldi - next_out);
3521 Colorize(ColorChallenge, FALSE);
3522 curColor = ColorChallenge;
3528 if (looking_at(buf, &i, "* offers you") ||
3529 looking_at(buf, &i, "* offers to be") ||
3530 looking_at(buf, &i, "* would like to") ||
3531 looking_at(buf, &i, "* requests to") ||
3532 looking_at(buf, &i, "Your opponent offers") ||
3533 looking_at(buf, &i, "Your opponent requests")) {
3535 if (appData.colorize) {
3536 if (oldi > next_out) {
3537 SendToPlayer(&buf[next_out], oldi - next_out);
3540 Colorize(ColorRequest, FALSE);
3541 curColor = ColorRequest;
3546 if (looking_at(buf, &i, "* (*) seeking")) {
3547 if (appData.colorize) {
3548 if (oldi > next_out) {
3549 SendToPlayer(&buf[next_out], oldi - next_out);
3552 Colorize(ColorSeek, FALSE);
3553 curColor = ColorSeek;
3558 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3560 if (looking_at(buf, &i, "\\ ")) {
3561 if (prevColor != ColorNormal) {
3562 if (oldi > next_out) {
3563 SendToPlayer(&buf[next_out], oldi - next_out);
3566 Colorize(prevColor, TRUE);
3567 curColor = prevColor;
3569 if (savingComment) {
3570 parse_pos = i - oldi;
3571 memcpy(parse, &buf[oldi], parse_pos);
3572 parse[parse_pos] = NULLCHAR;
3573 started = STARTED_COMMENT;
3574 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3575 chattingPartner = savingComment - 3; // kludge to remember the box
3577 started = STARTED_CHATTER;
3582 if (looking_at(buf, &i, "Black Strength :") ||
3583 looking_at(buf, &i, "<<< style 10 board >>>") ||
3584 looking_at(buf, &i, "<10>") ||
3585 looking_at(buf, &i, "#@#")) {
3586 /* Wrong board style */
3588 SendToICS(ics_prefix);
3589 SendToICS("set style 12\n");
3590 SendToICS(ics_prefix);
3591 SendToICS("refresh\n");
3595 if (looking_at(buf, &i, "login:")) {
3596 if (!have_sent_ICS_logon) {
3598 have_sent_ICS_logon = 1;
3599 else // no init script was found
3600 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3601 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3602 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3607 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3608 (looking_at(buf, &i, "\n<12> ") ||
3609 looking_at(buf, &i, "<12> "))) {
3611 if (oldi > next_out) {
3612 SendToPlayer(&buf[next_out], oldi - next_out);
3615 started = STARTED_BOARD;
3620 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3621 looking_at(buf, &i, "<b1> ")) {
3622 if (oldi > next_out) {
3623 SendToPlayer(&buf[next_out], oldi - next_out);
3626 started = STARTED_HOLDINGS;
3631 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3633 /* Header for a move list -- first line */
3635 switch (ics_getting_history) {
3639 case BeginningOfGame:
3640 /* User typed "moves" or "oldmoves" while we
3641 were idle. Pretend we asked for these
3642 moves and soak them up so user can step
3643 through them and/or save them.
3646 gameMode = IcsObserving;
3649 ics_getting_history = H_GOT_UNREQ_HEADER;
3651 case EditGame: /*?*/
3652 case EditPosition: /*?*/
3653 /* Should above feature work in these modes too? */
3654 /* For now it doesn't */
3655 ics_getting_history = H_GOT_UNWANTED_HEADER;
3658 ics_getting_history = H_GOT_UNWANTED_HEADER;
3663 /* Is this the right one? */
3664 if (gameInfo.white && gameInfo.black &&
3665 strcmp(gameInfo.white, star_match[0]) == 0 &&
3666 strcmp(gameInfo.black, star_match[2]) == 0) {
3668 ics_getting_history = H_GOT_REQ_HEADER;
3671 case H_GOT_REQ_HEADER:
3672 case H_GOT_UNREQ_HEADER:
3673 case H_GOT_UNWANTED_HEADER:
3674 case H_GETTING_MOVES:
3675 /* Should not happen */
3676 DisplayError(_("Error gathering move list: two headers"), 0);
3677 ics_getting_history = H_FALSE;
3681 /* Save player ratings into gameInfo if needed */
3682 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3683 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3684 (gameInfo.whiteRating == -1 ||
3685 gameInfo.blackRating == -1)) {
3687 gameInfo.whiteRating = string_to_rating(star_match[1]);
3688 gameInfo.blackRating = string_to_rating(star_match[3]);
3689 if (appData.debugMode)
3690 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3691 gameInfo.whiteRating, gameInfo.blackRating);
3696 if (looking_at(buf, &i,
3697 "* * match, initial time: * minute*, increment: * second")) {
3698 /* Header for a move list -- second line */
3699 /* Initial board will follow if this is a wild game */
3700 if (gameInfo.event != NULL) free(gameInfo.event);
3701 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3702 gameInfo.event = StrSave(str);
3703 /* [HGM] we switched variant. Translate boards if needed. */
3704 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3708 if (looking_at(buf, &i, "Move ")) {
3709 /* Beginning of a move list */
3710 switch (ics_getting_history) {
3712 /* Normally should not happen */
3713 /* Maybe user hit reset while we were parsing */
3716 /* Happens if we are ignoring a move list that is not
3717 * the one we just requested. Common if the user
3718 * tries to observe two games without turning off
3721 case H_GETTING_MOVES:
3722 /* Should not happen */
3723 DisplayError(_("Error gathering move list: nested"), 0);
3724 ics_getting_history = H_FALSE;
3726 case H_GOT_REQ_HEADER:
3727 ics_getting_history = H_GETTING_MOVES;
3728 started = STARTED_MOVES;
3730 if (oldi > next_out) {
3731 SendToPlayer(&buf[next_out], oldi - next_out);
3734 case H_GOT_UNREQ_HEADER:
3735 ics_getting_history = H_GETTING_MOVES;
3736 started = STARTED_MOVES_NOHIDE;
3739 case H_GOT_UNWANTED_HEADER:
3740 ics_getting_history = H_FALSE;
3746 if (looking_at(buf, &i, "% ") ||
3747 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3748 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3749 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3750 soughtPending = FALSE;
3754 if(suppressKibitz) next_out = i;
3755 savingComment = FALSE;
3759 case STARTED_MOVES_NOHIDE:
3760 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3761 parse[parse_pos + i - oldi] = NULLCHAR;
3762 ParseGameHistory(parse);
3764 if (appData.zippyPlay && first.initDone) {
3765 FeedMovesToProgram(&first, forwardMostMove);
3766 if (gameMode == IcsPlayingWhite) {
3767 if (WhiteOnMove(forwardMostMove)) {
3768 if (first.sendTime) {
3769 if (first.useColors) {
3770 SendToProgram("black\n", &first);
3772 SendTimeRemaining(&first, TRUE);
3774 if (first.useColors) {
3775 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3777 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3778 first.maybeThinking = TRUE;
3780 if (first.usePlayother) {
3781 if (first.sendTime) {
3782 SendTimeRemaining(&first, TRUE);
3784 SendToProgram("playother\n", &first);
3790 } else if (gameMode == IcsPlayingBlack) {
3791 if (!WhiteOnMove(forwardMostMove)) {
3792 if (first.sendTime) {
3793 if (first.useColors) {
3794 SendToProgram("white\n", &first);
3796 SendTimeRemaining(&first, FALSE);
3798 if (first.useColors) {
3799 SendToProgram("black\n", &first);
3801 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3802 first.maybeThinking = TRUE;
3804 if (first.usePlayother) {
3805 if (first.sendTime) {
3806 SendTimeRemaining(&first, FALSE);
3808 SendToProgram("playother\n", &first);
3817 if (gameMode == IcsObserving && ics_gamenum == -1) {
3818 /* Moves came from oldmoves or moves command
3819 while we weren't doing anything else.
3821 currentMove = forwardMostMove;
3822 ClearHighlights();/*!!could figure this out*/
3823 flipView = appData.flipView;
3824 DrawPosition(TRUE, boards[currentMove]);
3825 DisplayBothClocks();
3826 snprintf(str, MSG_SIZ, "%s %s %s",
3827 gameInfo.white, _("vs."), gameInfo.black);
3831 /* Moves were history of an active game */
3832 if (gameInfo.resultDetails != NULL) {
3833 free(gameInfo.resultDetails);
3834 gameInfo.resultDetails = NULL;
3837 HistorySet(parseList, backwardMostMove,
3838 forwardMostMove, currentMove-1);
3839 DisplayMove(currentMove - 1);
3840 if (started == STARTED_MOVES) next_out = i;
3841 started = STARTED_NONE;
3842 ics_getting_history = H_FALSE;
3845 case STARTED_OBSERVE:
3846 started = STARTED_NONE;
3847 SendToICS(ics_prefix);
3848 SendToICS("refresh\n");
3854 if(bookHit) { // [HGM] book: simulate book reply
3855 static char bookMove[MSG_SIZ]; // a bit generous?
3857 programStats.nodes = programStats.depth = programStats.time =
3858 programStats.score = programStats.got_only_move = 0;
3859 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3861 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3862 strcat(bookMove, bookHit);
3863 HandleMachineMove(bookMove, &first);
3868 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3869 started == STARTED_HOLDINGS ||
3870 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3871 /* Accumulate characters in move list or board */
3872 parse[parse_pos++] = buf[i];
3875 /* Start of game messages. Mostly we detect start of game