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 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278 static int initPing = -1;
280 /* States for ics_getting_history */
282 #define H_REQUESTED 1
283 #define H_GOT_REQ_HEADER 2
284 #define H_GOT_UNREQ_HEADER 3
285 #define H_GETTING_MOVES 4
286 #define H_GOT_UNWANTED_HEADER 5
288 /* whosays values for GameEnds */
297 /* Maximum number of games in a cmail message */
298 #define CMAIL_MAX_GAMES 20
300 /* Different types of move when calling RegisterMove */
302 #define CMAIL_RESIGN 1
304 #define CMAIL_ACCEPT 3
306 /* Different types of result to remember for each game */
307 #define CMAIL_NOT_RESULT 0
308 #define CMAIL_OLD_RESULT 1
309 #define CMAIL_NEW_RESULT 2
311 /* Telnet protocol constants */
322 safeStrCpy (char *dst, const char *src, size_t count)
325 assert( dst != NULL );
326 assert( src != NULL );
329 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
330 if( i == count && dst[count-1] != NULLCHAR)
332 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
333 if(appData.debugMode)
334 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
340 /* Some compiler can't cast u64 to double
341 * This function do the job for us:
343 * We use the highest bit for cast, this only
344 * works if the highest bit is not
345 * in use (This should not happen)
347 * We used this for all compiler
350 u64ToDouble (u64 value)
353 u64 tmp = value & u64Const(0x7fffffffffffffff);
354 r = (double)(s64)tmp;
355 if (value & u64Const(0x8000000000000000))
356 r += 9.2233720368547758080e18; /* 2^63 */
360 /* Fake up flags for now, as we aren't keeping track of castling
361 availability yet. [HGM] Change of logic: the flag now only
362 indicates the type of castlings allowed by the rule of the game.
363 The actual rights themselves are maintained in the array
364 castlingRights, as part of the game history, and are not probed
370 int flags = F_ALL_CASTLE_OK;
371 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
372 switch (gameInfo.variant) {
374 flags &= ~F_ALL_CASTLE_OK;
375 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
376 flags |= F_IGNORE_CHECK;
378 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
381 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
383 case VariantKriegspiel:
384 flags |= F_KRIEGSPIEL_CAPTURE;
386 case VariantCapaRandom:
387 case VariantFischeRandom:
388 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
389 case VariantNoCastle:
390 case VariantShatranj:
395 flags &= ~F_ALL_CASTLE_OK;
403 FILE *gameFileFP, *debugFP, *serverFP;
404 char *currentDebugFile; // [HGM] debug split: to remember name
407 [AS] Note: sometimes, the sscanf() function is used to parse the input
408 into a fixed-size buffer. Because of this, we must be prepared to
409 receive strings as long as the size of the input buffer, which is currently
410 set to 4K for Windows and 8K for the rest.
411 So, we must either allocate sufficiently large buffers here, or
412 reduce the size of the input buffer in the input reading part.
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
419 ChessProgramState first, second, pairing;
421 /* premove variables */
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
431 char *ics_prefix = "$";
432 enum ICS_TYPE ics_type = ICS_GENERIC;
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey, controlKey; // [HGM] set by mouse handler
462 int have_sent_ICS_logon = 0;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
466 Boolean adjustedClock;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE, startingEngine = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
476 /* animateTraining preserves the state of appData.animate
477 * when Training mode is activated. This allows the
478 * response to be animated when appData.animate == TRUE and
479 * appData.animateDragging == TRUE.
481 Boolean animateTraining;
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char initialRights[BOARD_FILES];
491 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int initialRulePlies, FENrulePlies;
493 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 Boolean shuffleOpenings;
496 int mute; // mute all sounds
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
514 ChessSquare FIDEArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackBishop, BlackKnight, BlackRook }
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525 BlackKing, BlackKing, BlackKnight, BlackRook }
528 ChessSquare KnightmateArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531 { BlackRook, BlackMan, BlackBishop, BlackQueen,
532 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackMan, BlackFerz,
560 BlackKing, BlackMan, BlackKnight, BlackRook }
563 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
564 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
565 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
566 { BlackRook, BlackKnight, BlackMan, BlackFerz,
567 BlackKing, BlackMan, BlackKnight, BlackRook }
570 ChessSquare lionArray[2][BOARD_FILES] = {
571 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
572 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573 { BlackRook, BlackLion, BlackBishop, BlackQueen,
574 BlackKing, BlackBishop, BlackKnight, BlackRook }
578 #if (BOARD_FILES>=10)
579 ChessSquare ShogiArray[2][BOARD_FILES] = {
580 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
581 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
582 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
583 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
586 ChessSquare XiangqiArray[2][BOARD_FILES] = {
587 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
588 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
590 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
593 ChessSquare CapablancaArray[2][BOARD_FILES] = {
594 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
595 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
596 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
597 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
600 ChessSquare GreatArray[2][BOARD_FILES] = {
601 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
602 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
603 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
604 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
607 ChessSquare JanusArray[2][BOARD_FILES] = {
608 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
609 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
610 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
611 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
614 ChessSquare GrandArray[2][BOARD_FILES] = {
615 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
616 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
617 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
618 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
621 ChessSquare ChuChessArray[2][BOARD_FILES] = {
622 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
623 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
624 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
625 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
629 ChessSquare GothicArray[2][BOARD_FILES] = {
630 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
631 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
632 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
633 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
636 #define GothicArray CapablancaArray
640 ChessSquare FalconArray[2][BOARD_FILES] = {
641 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
642 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
643 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
644 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
647 #define FalconArray CapablancaArray
650 #else // !(BOARD_FILES>=10)
651 #define XiangqiPosition FIDEArray
652 #define CapablancaArray FIDEArray
653 #define GothicArray FIDEArray
654 #define GreatArray FIDEArray
655 #endif // !(BOARD_FILES>=10)
657 #if (BOARD_FILES>=12)
658 ChessSquare CourierArray[2][BOARD_FILES] = {
659 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
660 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
661 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
662 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
664 ChessSquare ChuArray[6][BOARD_FILES] = {
665 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
666 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
667 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
668 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
669 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
670 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
671 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
672 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
673 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
674 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
675 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
676 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
678 #else // !(BOARD_FILES>=12)
679 #define CourierArray CapablancaArray
680 #define ChuArray CapablancaArray
681 #endif // !(BOARD_FILES>=12)
684 Board initialPosition;
687 /* Convert str to a rating. Checks for special cases of "----",
689 "++++", etc. Also strips ()'s */
691 string_to_rating (char *str)
693 while(*str && !isdigit(*str)) ++str;
695 return 0; /* One of the special "no rating" cases */
703 /* Init programStats */
704 programStats.movelist[0] = 0;
705 programStats.depth = 0;
706 programStats.nr_moves = 0;
707 programStats.moves_left = 0;
708 programStats.nodes = 0;
709 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
710 programStats.score = 0;
711 programStats.got_only_move = 0;
712 programStats.got_fail = 0;
713 programStats.line_is_book = 0;
718 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
719 if (appData.firstPlaysBlack) {
720 first.twoMachinesColor = "black\n";
721 second.twoMachinesColor = "white\n";
723 first.twoMachinesColor = "white\n";
724 second.twoMachinesColor = "black\n";
727 first.other = &second;
728 second.other = &first;
731 if(appData.timeOddsMode) {
732 norm = appData.timeOdds[0];
733 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
735 first.timeOdds = appData.timeOdds[0]/norm;
736 second.timeOdds = appData.timeOdds[1]/norm;
739 if(programVersion) free(programVersion);
740 if (appData.noChessProgram) {
741 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
742 sprintf(programVersion, "%s", PACKAGE_STRING);
744 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
745 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
746 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
751 UnloadEngine (ChessProgramState *cps)
753 /* Kill off first chess program */
754 if (cps->isr != NULL)
755 RemoveInputSource(cps->isr);
758 if (cps->pr != NoProc) {
760 DoSleep( appData.delayBeforeQuit );
761 SendToProgram("quit\n", cps);
762 DoSleep( appData.delayAfterQuit );
763 DestroyChildProcess(cps->pr, cps->useSigterm);
766 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
770 ClearOptions (ChessProgramState *cps)
773 cps->nrOptions = cps->comboCnt = 0;
774 for(i=0; i<MAX_OPTIONS; i++) {
775 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
776 cps->option[i].textValue = 0;
780 char *engineNames[] = {
781 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
782 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
784 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
785 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
790 InitEngine (ChessProgramState *cps, int n)
791 { // [HGM] all engine initialiation put in a function that does one engine
795 cps->which = engineNames[n];
796 cps->maybeThinking = FALSE;
800 cps->sendDrawOffers = 1;
802 cps->program = appData.chessProgram[n];
803 cps->host = appData.host[n];
804 cps->dir = appData.directory[n];
805 cps->initString = appData.engInitString[n];
806 cps->computerString = appData.computerString[n];
807 cps->useSigint = TRUE;
808 cps->useSigterm = TRUE;
809 cps->reuse = appData.reuse[n];
810 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
811 cps->useSetboard = FALSE;
813 cps->usePing = FALSE;
816 cps->usePlayother = FALSE;
817 cps->useColors = TRUE;
818 cps->useUsermove = FALSE;
819 cps->sendICS = FALSE;
820 cps->sendName = appData.icsActive;
821 cps->sdKludge = FALSE;
822 cps->stKludge = FALSE;
823 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
824 TidyProgramName(cps->program, cps->host, cps->tidy);
826 ASSIGN(cps->variants, appData.variant);
827 cps->analysisSupport = 2; /* detect */
828 cps->analyzing = FALSE;
829 cps->initDone = FALSE;
832 /* New features added by Tord: */
833 cps->useFEN960 = FALSE;
834 cps->useOOCastle = TRUE;
835 /* End of new features added by Tord. */
836 cps->fenOverride = appData.fenOverride[n];
838 /* [HGM] time odds: set factor for each machine */
839 cps->timeOdds = appData.timeOdds[n];
841 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
842 cps->accumulateTC = appData.accumulateTC[n];
843 cps->maxNrOfSessions = 1;
848 cps->supportsNPS = UNKNOWN;
849 cps->memSize = FALSE;
850 cps->maxCores = FALSE;
851 ASSIGN(cps->egtFormats, "");
854 cps->optionSettings = appData.engOptions[n];
856 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
857 cps->isUCI = appData.isUCI[n]; /* [AS] */
858 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
861 if (appData.protocolVersion[n] > PROTOVER
862 || appData.protocolVersion[n] < 1)
867 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
868 appData.protocolVersion[n]);
869 if( (len >= MSG_SIZ) && appData.debugMode )
870 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
872 DisplayFatalError(buf, 0, 2);
876 cps->protocolVersion = appData.protocolVersion[n];
879 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
880 ParseFeatures(appData.featureDefaults, cps);
883 ChessProgramState *savCps;
891 if(WaitForEngine(savCps, LoadEngine)) return;
892 CommonEngineInit(); // recalculate time odds
893 if(gameInfo.variant != StringToVariant(appData.variant)) {
894 // we changed variant when loading the engine; this forces us to reset
895 Reset(TRUE, savCps != &first);
896 oldMode = BeginningOfGame; // to prevent restoring old mode
898 InitChessProgram(savCps, FALSE);
899 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
900 DisplayMessage("", "");
901 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
902 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
905 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
909 ReplaceEngine (ChessProgramState *cps, int n)
911 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
913 if(oldMode != BeginningOfGame) EditGameEvent();
916 appData.noChessProgram = FALSE;
917 appData.clockMode = TRUE;
920 if(n) return; // only startup first engine immediately; second can wait
921 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
925 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
926 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
928 static char resetOptions[] =
929 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
930 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
931 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
932 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
935 FloatToFront(char **list, char *engineLine)
937 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
939 if(appData.recentEngines <= 0) return;
940 TidyProgramName(engineLine, "localhost", tidy+1);
941 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
942 strncpy(buf+1, *list, MSG_SIZ-50);
943 if(p = strstr(buf, tidy)) { // tidy name appears in list
944 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
945 while(*p++ = *++q); // squeeze out
947 strcat(tidy, buf+1); // put list behind tidy name
948 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
949 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
950 ASSIGN(*list, tidy+1);
953 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
956 Load (ChessProgramState *cps, int i)
958 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
959 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
960 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
961 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
962 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
963 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
964 appData.firstProtocolVersion = PROTOVER;
965 ParseArgsFromString(buf);
967 ReplaceEngine(cps, i);
968 FloatToFront(&appData.recentEngineList, engineLine);
972 while(q = strchr(p, SLASH)) p = q+1;
973 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
974 if(engineDir[0] != NULLCHAR) {
975 ASSIGN(appData.directory[i], engineDir); p = engineName;
976 } else if(p != engineName) { // derive directory from engine path, when not given
978 ASSIGN(appData.directory[i], engineName);
980 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
981 } else { ASSIGN(appData.directory[i], "."); }
982 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
984 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
985 snprintf(command, MSG_SIZ, "%s %s", p, params);
988 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
989 ASSIGN(appData.chessProgram[i], p);
990 appData.isUCI[i] = isUCI;
991 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
992 appData.hasOwnBookUCI[i] = hasBook;
993 if(!nickName[0]) useNick = FALSE;
994 if(useNick) ASSIGN(appData.pgnName[i], nickName);
998 q = firstChessProgramNames;
999 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1000 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1001 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1002 quote, p, quote, appData.directory[i],
1003 useNick ? " -fn \"" : "",
1004 useNick ? nickName : "",
1005 useNick ? "\"" : "",
1006 v1 ? " -firstProtocolVersion 1" : "",
1007 hasBook ? "" : " -fNoOwnBookUCI",
1008 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1009 storeVariant ? " -variant " : "",
1010 storeVariant ? VariantName(gameInfo.variant) : "");
1011 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1012 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1013 if(insert != q) insert[-1] = NULLCHAR;
1014 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1016 FloatToFront(&appData.recentEngineList, buf);
1018 ReplaceEngine(cps, i);
1024 int matched, min, sec;
1026 * Parse timeControl resource
1028 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1029 appData.movesPerSession)) {
1031 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1032 DisplayFatalError(buf, 0, 2);
1036 * Parse searchTime resource
1038 if (*appData.searchTime != NULLCHAR) {
1039 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1041 searchTime = min * 60;
1042 } else if (matched == 2) {
1043 searchTime = min * 60 + sec;
1046 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1047 DisplayFatalError(buf, 0, 2);
1056 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1057 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1059 GetTimeMark(&programStartTime);
1060 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1061 appData.seedBase = random() + (random()<<15);
1062 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1064 ClearProgramStats();
1065 programStats.ok_to_send = 1;
1066 programStats.seen_stat = 0;
1069 * Initialize game list
1075 * Internet chess server status
1077 if (appData.icsActive) {
1078 appData.matchMode = FALSE;
1079 appData.matchGames = 0;
1081 appData.noChessProgram = !appData.zippyPlay;
1083 appData.zippyPlay = FALSE;
1084 appData.zippyTalk = FALSE;
1085 appData.noChessProgram = TRUE;
1087 if (*appData.icsHelper != NULLCHAR) {
1088 appData.useTelnet = TRUE;
1089 appData.telnetProgram = appData.icsHelper;
1092 appData.zippyTalk = appData.zippyPlay = FALSE;
1095 /* [AS] Initialize pv info list [HGM] and game state */
1099 for( i=0; i<=framePtr; i++ ) {
1100 pvInfoList[i].depth = -1;
1101 boards[i][EP_STATUS] = EP_NONE;
1102 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1108 /* [AS] Adjudication threshold */
1109 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1111 InitEngine(&first, 0);
1112 InitEngine(&second, 1);
1115 pairing.which = "pairing"; // pairing engine
1116 pairing.pr = NoProc;
1118 pairing.program = appData.pairingEngine;
1119 pairing.host = "localhost";
1122 if (appData.icsActive) {
1123 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1124 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1125 appData.clockMode = FALSE;
1126 first.sendTime = second.sendTime = 0;
1130 /* Override some settings from environment variables, for backward
1131 compatibility. Unfortunately it's not feasible to have the env
1132 vars just set defaults, at least in xboard. Ugh.
1134 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1139 if (!appData.icsActive) {
1143 /* Check for variants that are supported only in ICS mode,
1144 or not at all. Some that are accepted here nevertheless
1145 have bugs; see comments below.
1147 VariantClass variant = StringToVariant(appData.variant);
1149 case VariantBughouse: /* need four players and two boards */
1150 case VariantKriegspiel: /* need to hide pieces and move details */
1151 /* case VariantFischeRandom: (Fabien: moved below) */
1152 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1153 if( (len >= MSG_SIZ) && appData.debugMode )
1154 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1156 DisplayFatalError(buf, 0, 2);
1159 case VariantUnknown:
1160 case VariantLoadable:
1170 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1171 if( (len >= MSG_SIZ) && appData.debugMode )
1172 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1174 DisplayFatalError(buf, 0, 2);
1177 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1178 case VariantFairy: /* [HGM] TestLegality definitely off! */
1179 case VariantGothic: /* [HGM] should work */
1180 case VariantCapablanca: /* [HGM] should work */
1181 case VariantCourier: /* [HGM] initial forced moves not implemented */
1182 case VariantShogi: /* [HGM] could still mate with pawn drop */
1183 case VariantChu: /* [HGM] experimental */
1184 case VariantKnightmate: /* [HGM] should work */
1185 case VariantCylinder: /* [HGM] untested */
1186 case VariantFalcon: /* [HGM] untested */
1187 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1188 offboard interposition not understood */
1189 case VariantNormal: /* definitely works! */
1190 case VariantWildCastle: /* pieces not automatically shuffled */
1191 case VariantNoCastle: /* pieces not automatically shuffled */
1192 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1193 case VariantLosers: /* should work except for win condition,
1194 and doesn't know captures are mandatory */
1195 case VariantSuicide: /* should work except for win condition,
1196 and doesn't know captures are mandatory */
1197 case VariantGiveaway: /* should work except for win condition,
1198 and doesn't know captures are mandatory */
1199 case VariantTwoKings: /* should work */
1200 case VariantAtomic: /* should work except for win condition */
1201 case Variant3Check: /* should work except for win condition */
1202 case VariantShatranj: /* should work except for all win conditions */
1203 case VariantMakruk: /* should work except for draw countdown */
1204 case VariantASEAN : /* should work except for draw countdown */
1205 case VariantBerolina: /* might work if TestLegality is off */
1206 case VariantCapaRandom: /* should work */
1207 case VariantJanus: /* should work */
1208 case VariantSuper: /* experimental */
1209 case VariantGreat: /* experimental, requires legality testing to be off */
1210 case VariantSChess: /* S-Chess, should work */
1211 case VariantGrand: /* should work */
1212 case VariantSpartan: /* should work */
1213 case VariantLion: /* should work */
1214 case VariantChuChess: /* should work */
1222 NextIntegerFromString (char ** str, long * value)
1227 while( *s == ' ' || *s == '\t' ) {
1233 if( *s >= '0' && *s <= '9' ) {
1234 while( *s >= '0' && *s <= '9' ) {
1235 *value = *value * 10 + (*s - '0');
1248 NextTimeControlFromString (char ** str, long * value)
1251 int result = NextIntegerFromString( str, &temp );
1254 *value = temp * 60; /* Minutes */
1255 if( **str == ':' ) {
1257 result = NextIntegerFromString( str, &temp );
1258 *value += temp; /* Seconds */
1266 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1267 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1268 int result = -1, type = 0; long temp, temp2;
1270 if(**str != ':') return -1; // old params remain in force!
1272 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1273 if( NextIntegerFromString( str, &temp ) ) return -1;
1274 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1277 /* time only: incremental or sudden-death time control */
1278 if(**str == '+') { /* increment follows; read it */
1280 if(**str == '!') type = *(*str)++; // Bronstein TC
1281 if(result = NextIntegerFromString( str, &temp2)) return -1;
1282 *inc = temp2 * 1000;
1283 if(**str == '.') { // read fraction of increment
1284 char *start = ++(*str);
1285 if(result = NextIntegerFromString( str, &temp2)) return -1;
1287 while(start++ < *str) temp2 /= 10;
1291 *moves = 0; *tc = temp * 1000; *incType = type;
1295 (*str)++; /* classical time control */
1296 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1308 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1309 { /* [HGM] get time to add from the multi-session time-control string */
1310 int incType, moves=1; /* kludge to force reading of first session */
1311 long time, increment;
1314 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1316 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1317 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1318 if(movenr == -1) return time; /* last move before new session */
1319 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1320 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1321 if(!moves) return increment; /* current session is incremental */
1322 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1323 } while(movenr >= -1); /* try again for next session */
1325 return 0; // no new time quota on this move
1329 ParseTimeControl (char *tc, float ti, int mps)
1333 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1336 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1337 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1338 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1342 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1344 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1347 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1349 snprintf(buf, MSG_SIZ, ":%s", mytc);
1351 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1353 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1358 /* Parse second time control */
1361 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1369 timeControl_2 = tc2 * 1000;
1379 timeControl = tc1 * 1000;
1382 timeIncrement = ti * 1000; /* convert to ms */
1383 movesPerSession = 0;
1386 movesPerSession = mps;
1394 if (appData.debugMode) {
1395 # ifdef __GIT_VERSION
1396 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1398 fprintf(debugFP, "Version: %s\n", programVersion);
1401 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1403 set_cont_sequence(appData.wrapContSeq);
1404 if (appData.matchGames > 0) {
1405 appData.matchMode = TRUE;
1406 } else if (appData.matchMode) {
1407 appData.matchGames = 1;
1409 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1410 appData.matchGames = appData.sameColorGames;
1411 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1412 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1413 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1416 if (appData.noChessProgram || first.protocolVersion == 1) {
1419 /* kludge: allow timeout for initial "feature" commands */
1421 DisplayMessage("", _("Starting chess program"));
1422 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1427 CalculateIndex (int index, int gameNr)
1428 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1430 if(index > 0) return index; // fixed nmber
1431 if(index == 0) return 1;
1432 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1433 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1438 LoadGameOrPosition (int gameNr)
1439 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1440 if (*appData.loadGameFile != NULLCHAR) {
1441 if (!LoadGameFromFile(appData.loadGameFile,
1442 CalculateIndex(appData.loadGameIndex, gameNr),
1443 appData.loadGameFile, FALSE)) {
1444 DisplayFatalError(_("Bad game file"), 0, 1);
1447 } else if (*appData.loadPositionFile != NULLCHAR) {
1448 if (!LoadPositionFromFile(appData.loadPositionFile,
1449 CalculateIndex(appData.loadPositionIndex, gameNr),
1450 appData.loadPositionFile)) {
1451 DisplayFatalError(_("Bad position file"), 0, 1);
1459 ReserveGame (int gameNr, char resChar)
1461 FILE *tf = fopen(appData.tourneyFile, "r+");
1462 char *p, *q, c, buf[MSG_SIZ];
1463 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1464 safeStrCpy(buf, lastMsg, MSG_SIZ);
1465 DisplayMessage(_("Pick new game"), "");
1466 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1467 ParseArgsFromFile(tf);
1468 p = q = appData.results;
1469 if(appData.debugMode) {
1470 char *r = appData.participants;
1471 fprintf(debugFP, "results = '%s'\n", p);
1472 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1473 fprintf(debugFP, "\n");
1475 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1477 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1478 safeStrCpy(q, p, strlen(p) + 2);
1479 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1480 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1481 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1482 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1485 fseek(tf, -(strlen(p)+4), SEEK_END);
1487 if(c != '"') // depending on DOS or Unix line endings we can be one off
1488 fseek(tf, -(strlen(p)+2), SEEK_END);
1489 else fseek(tf, -(strlen(p)+3), SEEK_END);
1490 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1491 DisplayMessage(buf, "");
1492 free(p); appData.results = q;
1493 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1494 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1495 int round = appData.defaultMatchGames * appData.tourneyType;
1496 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1497 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1498 UnloadEngine(&first); // next game belongs to other pairing;
1499 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1501 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1505 MatchEvent (int mode)
1506 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1508 if(matchMode) { // already in match mode: switch it off
1510 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1513 // if(gameMode != BeginningOfGame) {
1514 // DisplayError(_("You can only start a match from the initial position."), 0);
1518 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1519 /* Set up machine vs. machine match */
1521 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1522 if(appData.tourneyFile[0]) {
1524 if(nextGame > appData.matchGames) {
1526 if(strchr(appData.results, '*') == NULL) {
1528 appData.tourneyCycles++;
1529 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1531 NextTourneyGame(-1, &dummy);
1533 if(nextGame <= appData.matchGames) {
1534 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1536 ScheduleDelayedEvent(NextMatchGame, 10000);
1541 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1542 DisplayError(buf, 0);
1543 appData.tourneyFile[0] = 0;
1547 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1548 DisplayFatalError(_("Can't have a match with no chess programs"),
1553 matchGame = roundNr = 1;
1554 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1558 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1561 InitBackEnd3 P((void))
1563 GameMode initialMode;
1567 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1568 !strcmp(appData.variant, "normal") && // no explicit variant request
1569 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1570 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1571 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1572 char c, *q = first.variants, *p = strchr(q, ',');
1573 if(p) *p = NULLCHAR;
1574 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1576 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1577 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1578 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1579 Reset(TRUE, FALSE); // and re-initialize
1584 InitChessProgram(&first, startedFromSetupPosition);
1586 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1587 free(programVersion);
1588 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1589 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1590 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1593 if (appData.icsActive) {
1595 /* [DM] Make a console window if needed [HGM] merged ifs */
1601 if (*appData.icsCommPort != NULLCHAR)
1602 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1603 appData.icsCommPort);
1605 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1606 appData.icsHost, appData.icsPort);
1608 if( (len >= MSG_SIZ) && appData.debugMode )
1609 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1611 DisplayFatalError(buf, err, 1);
1616 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1618 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1619 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1620 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1621 } else if (appData.noChessProgram) {
1627 if (*appData.cmailGameName != NULLCHAR) {
1629 OpenLoopback(&cmailPR);
1631 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1635 DisplayMessage("", "");
1636 if (StrCaseCmp(appData.initialMode, "") == 0) {
1637 initialMode = BeginningOfGame;
1638 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1639 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1640 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1641 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1644 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1645 initialMode = TwoMachinesPlay;
1646 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1647 initialMode = AnalyzeFile;
1648 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1649 initialMode = AnalyzeMode;
1650 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1651 initialMode = MachinePlaysWhite;
1652 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1653 initialMode = MachinePlaysBlack;
1654 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1655 initialMode = EditGame;
1656 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1657 initialMode = EditPosition;
1658 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1659 initialMode = Training;
1661 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1662 if( (len >= MSG_SIZ) && appData.debugMode )
1663 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1665 DisplayFatalError(buf, 0, 2);
1669 if (appData.matchMode) {
1670 if(appData.tourneyFile[0]) { // start tourney from command line
1672 if(f = fopen(appData.tourneyFile, "r")) {
1673 ParseArgsFromFile(f); // make sure tourney parmeters re known
1675 appData.clockMode = TRUE;
1677 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1680 } else if (*appData.cmailGameName != NULLCHAR) {
1681 /* Set up cmail mode */
1682 ReloadCmailMsgEvent(TRUE);
1684 /* Set up other modes */
1685 if (initialMode == AnalyzeFile) {
1686 if (*appData.loadGameFile == NULLCHAR) {
1687 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1691 if (*appData.loadGameFile != NULLCHAR) {
1692 (void) LoadGameFromFile(appData.loadGameFile,
1693 appData.loadGameIndex,
1694 appData.loadGameFile, TRUE);
1695 } else if (*appData.loadPositionFile != NULLCHAR) {
1696 (void) LoadPositionFromFile(appData.loadPositionFile,
1697 appData.loadPositionIndex,
1698 appData.loadPositionFile);
1699 /* [HGM] try to make self-starting even after FEN load */
1700 /* to allow automatic setup of fairy variants with wtm */
1701 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1702 gameMode = BeginningOfGame;
1703 setboardSpoiledMachineBlack = 1;
1705 /* [HGM] loadPos: make that every new game uses the setup */
1706 /* from file as long as we do not switch variant */
1707 if(!blackPlaysFirst) {
1708 startedFromPositionFile = TRUE;
1709 CopyBoard(filePosition, boards[0]);
1712 if (initialMode == AnalyzeMode) {
1713 if (appData.noChessProgram) {
1714 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1717 if (appData.icsActive) {
1718 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1722 } else if (initialMode == AnalyzeFile) {
1723 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1724 ShowThinkingEvent();
1726 AnalysisPeriodicEvent(1);
1727 } else if (initialMode == MachinePlaysWhite) {
1728 if (appData.noChessProgram) {
1729 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1733 if (appData.icsActive) {
1734 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1738 MachineWhiteEvent();
1739 } else if (initialMode == MachinePlaysBlack) {
1740 if (appData.noChessProgram) {
1741 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1745 if (appData.icsActive) {
1746 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1750 MachineBlackEvent();
1751 } else if (initialMode == TwoMachinesPlay) {
1752 if (appData.noChessProgram) {
1753 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1757 if (appData.icsActive) {
1758 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1763 } else if (initialMode == EditGame) {
1765 } else if (initialMode == EditPosition) {
1766 EditPositionEvent();
1767 } else if (initialMode == Training) {
1768 if (*appData.loadGameFile == NULLCHAR) {
1769 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1778 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1780 DisplayBook(current+1);
1782 MoveHistorySet( movelist, first, last, current, pvInfoList );
1784 EvalGraphSet( first, last, current, pvInfoList );
1786 MakeEngineOutputTitle();
1790 * Establish will establish a contact to a remote host.port.
1791 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1792 * used to talk to the host.
1793 * Returns 0 if okay, error code if not.
1800 if (*appData.icsCommPort != NULLCHAR) {
1801 /* Talk to the host through a serial comm port */
1802 return OpenCommPort(appData.icsCommPort, &icsPR);
1804 } else if (*appData.gateway != NULLCHAR) {
1805 if (*appData.remoteShell == NULLCHAR) {
1806 /* Use the rcmd protocol to run telnet program on a gateway host */
1807 snprintf(buf, sizeof(buf), "%s %s %s",
1808 appData.telnetProgram, appData.icsHost, appData.icsPort);
1809 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1812 /* Use the rsh program to run telnet program on a gateway host */
1813 if (*appData.remoteUser == NULLCHAR) {
1814 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1815 appData.gateway, appData.telnetProgram,
1816 appData.icsHost, appData.icsPort);
1818 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1819 appData.remoteShell, appData.gateway,
1820 appData.remoteUser, appData.telnetProgram,
1821 appData.icsHost, appData.icsPort);
1823 return StartChildProcess(buf, "", &icsPR);
1826 } else if (appData.useTelnet) {
1827 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1830 /* TCP socket interface differs somewhat between
1831 Unix and NT; handle details in the front end.
1833 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1838 EscapeExpand (char *p, char *q)
1839 { // [HGM] initstring: routine to shape up string arguments
1840 while(*p++ = *q++) if(p[-1] == '\\')
1842 case 'n': p[-1] = '\n'; break;
1843 case 'r': p[-1] = '\r'; break;
1844 case 't': p[-1] = '\t'; break;
1845 case '\\': p[-1] = '\\'; break;
1846 case 0: *p = 0; return;
1847 default: p[-1] = q[-1]; break;
1852 show_bytes (FILE *fp, char *buf, int count)
1855 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1856 fprintf(fp, "\\%03o", *buf & 0xff);
1865 /* Returns an errno value */
1867 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1869 char buf[8192], *p, *q, *buflim;
1870 int left, newcount, outcount;
1872 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1873 *appData.gateway != NULLCHAR) {
1874 if (appData.debugMode) {
1875 fprintf(debugFP, ">ICS: ");
1876 show_bytes(debugFP, message, count);
1877 fprintf(debugFP, "\n");
1879 return OutputToProcess(pr, message, count, outError);
1882 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1889 if (appData.debugMode) {
1890 fprintf(debugFP, ">ICS: ");
1891 show_bytes(debugFP, buf, newcount);
1892 fprintf(debugFP, "\n");
1894 outcount = OutputToProcess(pr, buf, newcount, outError);
1895 if (outcount < newcount) return -1; /* to be sure */
1902 } else if (((unsigned char) *p) == TN_IAC) {
1903 *q++ = (char) TN_IAC;
1910 if (appData.debugMode) {
1911 fprintf(debugFP, ">ICS: ");
1912 show_bytes(debugFP, buf, newcount);
1913 fprintf(debugFP, "\n");
1915 outcount = OutputToProcess(pr, buf, newcount, outError);
1916 if (outcount < newcount) return -1; /* to be sure */
1921 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1923 int outError, outCount;
1924 static int gotEof = 0;
1927 /* Pass data read from player on to ICS */
1930 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1931 if (outCount < count) {
1932 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1934 if(have_sent_ICS_logon == 2) {
1935 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1936 fprintf(ini, "%s", message);
1937 have_sent_ICS_logon = 3;
1939 have_sent_ICS_logon = 1;
1940 } else if(have_sent_ICS_logon == 3) {
1941 fprintf(ini, "%s", message);
1943 have_sent_ICS_logon = 1;
1945 } else if (count < 0) {
1946 RemoveInputSource(isr);
1947 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1948 } else if (gotEof++ > 0) {
1949 RemoveInputSource(isr);
1950 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1956 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1957 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1958 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1959 SendToICS("date\n");
1960 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1963 /* added routine for printf style output to ics */
1965 ics_printf (char *format, ...)
1967 char buffer[MSG_SIZ];
1970 va_start(args, format);
1971 vsnprintf(buffer, sizeof(buffer), format, args);
1972 buffer[sizeof(buffer)-1] = '\0';
1980 int count, outCount, outError;
1982 if (icsPR == NoProc) return;
1985 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1986 if (outCount < count) {
1987 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1991 /* This is used for sending logon scripts to the ICS. Sending
1992 without a delay causes problems when using timestamp on ICC
1993 (at least on my machine). */
1995 SendToICSDelayed (char *s, long msdelay)
1997 int count, outCount, outError;
1999 if (icsPR == NoProc) return;
2002 if (appData.debugMode) {
2003 fprintf(debugFP, ">ICS: ");
2004 show_bytes(debugFP, s, count);
2005 fprintf(debugFP, "\n");
2007 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2009 if (outCount < count) {
2010 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2015 /* Remove all highlighting escape sequences in s
2016 Also deletes any suffix starting with '('
2019 StripHighlightAndTitle (char *s)
2021 static char retbuf[MSG_SIZ];
2024 while (*s != NULLCHAR) {
2025 while (*s == '\033') {
2026 while (*s != NULLCHAR && !isalpha(*s)) s++;
2027 if (*s != NULLCHAR) s++;
2029 while (*s != NULLCHAR && *s != '\033') {
2030 if (*s == '(' || *s == '[') {
2041 /* Remove all highlighting escape sequences in s */
2043 StripHighlight (char *s)
2045 static char retbuf[MSG_SIZ];
2048 while (*s != NULLCHAR) {
2049 while (*s == '\033') {
2050 while (*s != NULLCHAR && !isalpha(*s)) s++;
2051 if (*s != NULLCHAR) s++;
2053 while (*s != NULLCHAR && *s != '\033') {
2061 char engineVariant[MSG_SIZ];
2062 char *variantNames[] = VARIANT_NAMES;
2064 VariantName (VariantClass v)
2066 if(v == VariantUnknown || *engineVariant) return engineVariant;
2067 return variantNames[v];
2071 /* Identify a variant from the strings the chess servers use or the
2072 PGN Variant tag names we use. */
2074 StringToVariant (char *e)
2078 VariantClass v = VariantNormal;
2079 int i, found = FALSE;
2085 /* [HGM] skip over optional board-size prefixes */
2086 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2087 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2088 while( *e++ != '_');
2091 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2095 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2096 if (p = StrCaseStr(e, variantNames[i])) {
2097 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2098 v = (VariantClass) i;
2105 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2106 || StrCaseStr(e, "wild/fr")
2107 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2108 v = VariantFischeRandom;
2109 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2110 (i = 1, p = StrCaseStr(e, "w"))) {
2112 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2119 case 0: /* FICS only, actually */
2121 /* Castling legal even if K starts on d-file */
2122 v = VariantWildCastle;
2127 /* Castling illegal even if K & R happen to start in
2128 normal positions. */
2129 v = VariantNoCastle;
2142 /* Castling legal iff K & R start in normal positions */
2148 /* Special wilds for position setup; unclear what to do here */
2149 v = VariantLoadable;
2152 /* Bizarre ICC game */
2153 v = VariantTwoKings;
2156 v = VariantKriegspiel;
2162 v = VariantFischeRandom;
2165 v = VariantCrazyhouse;
2168 v = VariantBughouse;
2174 /* Not quite the same as FICS suicide! */
2175 v = VariantGiveaway;
2181 v = VariantShatranj;
2184 /* Temporary names for future ICC types. The name *will* change in
2185 the next xboard/WinBoard release after ICC defines it. */
2223 v = VariantCapablanca;
2226 v = VariantKnightmate;
2232 v = VariantCylinder;
2238 v = VariantCapaRandom;
2241 v = VariantBerolina;
2253 /* Found "wild" or "w" in the string but no number;
2254 must assume it's normal chess. */
2258 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2259 if( (len >= MSG_SIZ) && appData.debugMode )
2260 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2262 DisplayError(buf, 0);
2268 if (appData.debugMode) {
2269 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2270 e, wnum, VariantName(v));
2275 static int leftover_start = 0, leftover_len = 0;
2276 char star_match[STAR_MATCH_N][MSG_SIZ];
2278 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2279 advance *index beyond it, and set leftover_start to the new value of
2280 *index; else return FALSE. If pattern contains the character '*', it
2281 matches any sequence of characters not containing '\r', '\n', or the
2282 character following the '*' (if any), and the matched sequence(s) are
2283 copied into star_match.
2286 looking_at ( char *buf, int *index, char *pattern)
2288 char *bufp = &buf[*index], *patternp = pattern;
2290 char *matchp = star_match[0];
2293 if (*patternp == NULLCHAR) {
2294 *index = leftover_start = bufp - buf;
2298 if (*bufp == NULLCHAR) return FALSE;
2299 if (*patternp == '*') {
2300 if (*bufp == *(patternp + 1)) {
2302 matchp = star_match[++star_count];
2306 } else if (*bufp == '\n' || *bufp == '\r') {
2308 if (*patternp == NULLCHAR)
2313 *matchp++ = *bufp++;
2317 if (*patternp != *bufp) return FALSE;
2324 SendToPlayer (char *data, int length)
2326 int error, outCount;
2327 outCount = OutputToProcess(NoProc, data, length, &error);
2328 if (outCount < length) {
2329 DisplayFatalError(_("Error writing to display"), error, 1);
2334 PackHolding (char packed[], char *holding)
2344 switch (runlength) {
2355 sprintf(q, "%d", runlength);
2367 /* Telnet protocol requests from the front end */
2369 TelnetRequest (unsigned char ddww, unsigned char option)
2371 unsigned char msg[3];
2372 int outCount, outError;
2374 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2376 if (appData.debugMode) {
2377 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2393 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2402 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2405 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2410 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2412 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2419 if (!appData.icsActive) return;
2420 TelnetRequest(TN_DO, TN_ECHO);
2426 if (!appData.icsActive) return;
2427 TelnetRequest(TN_DONT, TN_ECHO);
2431 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2433 /* put the holdings sent to us by the server on the board holdings area */
2434 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2438 if(gameInfo.holdingsWidth < 2) return;
2439 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2440 return; // prevent overwriting by pre-board holdings
2442 if( (int)lowestPiece >= BlackPawn ) {
2445 holdingsStartRow = BOARD_HEIGHT-1;
2448 holdingsColumn = BOARD_WIDTH-1;
2449 countsColumn = BOARD_WIDTH-2;
2450 holdingsStartRow = 0;
2454 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2455 board[i][holdingsColumn] = EmptySquare;
2456 board[i][countsColumn] = (ChessSquare) 0;
2458 while( (p=*holdings++) != NULLCHAR ) {
2459 piece = CharToPiece( ToUpper(p) );
2460 if(piece == EmptySquare) continue;
2461 /*j = (int) piece - (int) WhitePawn;*/
2462 j = PieceToNumber(piece);
2463 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2464 if(j < 0) continue; /* should not happen */
2465 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2466 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2467 board[holdingsStartRow+j*direction][countsColumn]++;
2473 VariantSwitch (Board board, VariantClass newVariant)
2475 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2476 static Board oldBoard;
2478 startedFromPositionFile = FALSE;
2479 if(gameInfo.variant == newVariant) return;
2481 /* [HGM] This routine is called each time an assignment is made to
2482 * gameInfo.variant during a game, to make sure the board sizes
2483 * are set to match the new variant. If that means adding or deleting
2484 * holdings, we shift the playing board accordingly
2485 * This kludge is needed because in ICS observe mode, we get boards
2486 * of an ongoing game without knowing the variant, and learn about the
2487 * latter only later. This can be because of the move list we requested,
2488 * in which case the game history is refilled from the beginning anyway,
2489 * but also when receiving holdings of a crazyhouse game. In the latter
2490 * case we want to add those holdings to the already received position.
2494 if (appData.debugMode) {
2495 fprintf(debugFP, "Switch board from %s to %s\n",
2496 VariantName(gameInfo.variant), VariantName(newVariant));
2497 setbuf(debugFP, NULL);
2499 shuffleOpenings = 0; /* [HGM] shuffle */
2500 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2504 newWidth = 9; newHeight = 9;
2505 gameInfo.holdingsSize = 7;
2506 case VariantBughouse:
2507 case VariantCrazyhouse:
2508 newHoldingsWidth = 2; break;
2512 newHoldingsWidth = 2;
2513 gameInfo.holdingsSize = 8;
2516 case VariantCapablanca:
2517 case VariantCapaRandom:
2520 newHoldingsWidth = gameInfo.holdingsSize = 0;
2523 if(newWidth != gameInfo.boardWidth ||
2524 newHeight != gameInfo.boardHeight ||
2525 newHoldingsWidth != gameInfo.holdingsWidth ) {
2527 /* shift position to new playing area, if needed */
2528 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2529 for(i=0; i<BOARD_HEIGHT; i++)
2530 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2531 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2533 for(i=0; i<newHeight; i++) {
2534 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2535 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2537 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2538 for(i=0; i<BOARD_HEIGHT; i++)
2539 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2540 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2543 board[HOLDINGS_SET] = 0;
2544 gameInfo.boardWidth = newWidth;
2545 gameInfo.boardHeight = newHeight;
2546 gameInfo.holdingsWidth = newHoldingsWidth;
2547 gameInfo.variant = newVariant;
2548 InitDrawingSizes(-2, 0);
2549 } else gameInfo.variant = newVariant;
2550 CopyBoard(oldBoard, board); // remember correctly formatted board
2551 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2552 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2555 static int loggedOn = FALSE;
2557 /*-- Game start info cache: --*/
2559 char gs_kind[MSG_SIZ];
2560 static char player1Name[128] = "";
2561 static char player2Name[128] = "";
2562 static char cont_seq[] = "\n\\ ";
2563 static int player1Rating = -1;
2564 static int player2Rating = -1;
2565 /*----------------------------*/
2567 ColorClass curColor = ColorNormal;
2568 int suppressKibitz = 0;
2571 Boolean soughtPending = FALSE;
2572 Boolean seekGraphUp;
2573 #define MAX_SEEK_ADS 200
2575 char *seekAdList[MAX_SEEK_ADS];
2576 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2577 float tcList[MAX_SEEK_ADS];
2578 char colorList[MAX_SEEK_ADS];
2579 int nrOfSeekAds = 0;
2580 int minRating = 1010, maxRating = 2800;
2581 int hMargin = 10, vMargin = 20, h, w;
2582 extern int squareSize, lineGap;
2587 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2588 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2589 if(r < minRating+100 && r >=0 ) r = minRating+100;
2590 if(r > maxRating) r = maxRating;
2591 if(tc < 1.f) tc = 1.f;
2592 if(tc > 95.f) tc = 95.f;
2593 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2594 y = ((double)r - minRating)/(maxRating - minRating)
2595 * (h-vMargin-squareSize/8-1) + vMargin;
2596 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2597 if(strstr(seekAdList[i], " u ")) color = 1;
2598 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2599 !strstr(seekAdList[i], "bullet") &&
2600 !strstr(seekAdList[i], "blitz") &&
2601 !strstr(seekAdList[i], "standard") ) color = 2;
2602 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2603 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2607 PlotSingleSeekAd (int i)
2613 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2615 char buf[MSG_SIZ], *ext = "";
2616 VariantClass v = StringToVariant(type);
2617 if(strstr(type, "wild")) {
2618 ext = type + 4; // append wild number
2619 if(v == VariantFischeRandom) type = "chess960"; else
2620 if(v == VariantLoadable) type = "setup"; else
2621 type = VariantName(v);
2623 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2624 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2625 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2626 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2627 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2628 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2629 seekNrList[nrOfSeekAds] = nr;
2630 zList[nrOfSeekAds] = 0;
2631 seekAdList[nrOfSeekAds++] = StrSave(buf);
2632 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2637 EraseSeekDot (int i)
2639 int x = xList[i], y = yList[i], d=squareSize/4, k;
2640 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2641 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2642 // now replot every dot that overlapped
2643 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2644 int xx = xList[k], yy = yList[k];
2645 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2646 DrawSeekDot(xx, yy, colorList[k]);
2651 RemoveSeekAd (int nr)
2654 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2656 if(seekAdList[i]) free(seekAdList[i]);
2657 seekAdList[i] = seekAdList[--nrOfSeekAds];
2658 seekNrList[i] = seekNrList[nrOfSeekAds];
2659 ratingList[i] = ratingList[nrOfSeekAds];
2660 colorList[i] = colorList[nrOfSeekAds];
2661 tcList[i] = tcList[nrOfSeekAds];
2662 xList[i] = xList[nrOfSeekAds];
2663 yList[i] = yList[nrOfSeekAds];
2664 zList[i] = zList[nrOfSeekAds];
2665 seekAdList[nrOfSeekAds] = NULL;
2671 MatchSoughtLine (char *line)
2673 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2674 int nr, base, inc, u=0; char dummy;
2676 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2677 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2679 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2680 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2681 // match: compact and save the line
2682 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2692 if(!seekGraphUp) return FALSE;
2693 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2694 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2696 DrawSeekBackground(0, 0, w, h);
2697 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2698 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2699 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2700 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2702 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2705 snprintf(buf, MSG_SIZ, "%d", i);
2706 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2709 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2710 for(i=1; i<100; i+=(i<10?1:5)) {
2711 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2712 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2713 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2715 snprintf(buf, MSG_SIZ, "%d", i);
2716 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2719 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2724 SeekGraphClick (ClickType click, int x, int y, int moving)
2726 static int lastDown = 0, displayed = 0, lastSecond;
2727 if(y < 0) return FALSE;
2728 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2729 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2730 if(!seekGraphUp) return FALSE;
2731 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2732 DrawPosition(TRUE, NULL);
2735 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2736 if(click == Release || moving) return FALSE;
2738 soughtPending = TRUE;
2739 SendToICS(ics_prefix);
2740 SendToICS("sought\n"); // should this be "sought all"?
2741 } else { // issue challenge based on clicked ad
2742 int dist = 10000; int i, closest = 0, second = 0;
2743 for(i=0; i<nrOfSeekAds; i++) {
2744 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2745 if(d < dist) { dist = d; closest = i; }
2746 second += (d - zList[i] < 120); // count in-range ads
2747 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2751 second = (second > 1);
2752 if(displayed != closest || second != lastSecond) {
2753 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2754 lastSecond = second; displayed = closest;
2756 if(click == Press) {
2757 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2760 } // on press 'hit', only show info
2761 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2762 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2763 SendToICS(ics_prefix);
2765 return TRUE; // let incoming board of started game pop down the graph
2766 } else if(click == Release) { // release 'miss' is ignored
2767 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2768 if(moving == 2) { // right up-click
2769 nrOfSeekAds = 0; // refresh graph
2770 soughtPending = TRUE;
2771 SendToICS(ics_prefix);
2772 SendToICS("sought\n"); // should this be "sought all"?
2775 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2776 // press miss or release hit 'pop down' seek graph
2777 seekGraphUp = FALSE;
2778 DrawPosition(TRUE, NULL);
2784 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2786 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2787 #define STARTED_NONE 0
2788 #define STARTED_MOVES 1
2789 #define STARTED_BOARD 2
2790 #define STARTED_OBSERVE 3
2791 #define STARTED_HOLDINGS 4
2792 #define STARTED_CHATTER 5
2793 #define STARTED_COMMENT 6
2794 #define STARTED_MOVES_NOHIDE 7
2796 static int started = STARTED_NONE;
2797 static char parse[20000];
2798 static int parse_pos = 0;
2799 static char buf[BUF_SIZE + 1];
2800 static int firstTime = TRUE, intfSet = FALSE;
2801 static ColorClass prevColor = ColorNormal;
2802 static int savingComment = FALSE;
2803 static int cmatch = 0; // continuation sequence match
2810 int backup; /* [DM] For zippy color lines */
2812 char talker[MSG_SIZ]; // [HGM] chat
2815 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2817 if (appData.debugMode) {
2819 fprintf(debugFP, "<ICS: ");
2820 show_bytes(debugFP, data, count);
2821 fprintf(debugFP, "\n");
2825 if (appData.debugMode) { int f = forwardMostMove;
2826 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2827 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2828 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2831 /* If last read ended with a partial line that we couldn't parse,
2832 prepend it to the new read and try again. */
2833 if (leftover_len > 0) {
2834 for (i=0; i<leftover_len; i++)
2835 buf[i] = buf[leftover_start + i];
2838 /* copy new characters into the buffer */
2839 bp = buf + leftover_len;
2840 buf_len=leftover_len;
2841 for (i=0; i<count; i++)
2844 if (data[i] == '\r')
2847 // join lines split by ICS?
2848 if (!appData.noJoin)
2851 Joining just consists of finding matches against the
2852 continuation sequence, and discarding that sequence
2853 if found instead of copying it. So, until a match
2854 fails, there's nothing to do since it might be the
2855 complete sequence, and thus, something we don't want
2858 if (data[i] == cont_seq[cmatch])
2861 if (cmatch == strlen(cont_seq))
2863 cmatch = 0; // complete match. just reset the counter
2866 it's possible for the ICS to not include the space
2867 at the end of the last word, making our [correct]
2868 join operation fuse two separate words. the server
2869 does this when the space occurs at the width setting.
2871 if (!buf_len || buf[buf_len-1] != ' ')
2882 match failed, so we have to copy what matched before
2883 falling through and copying this character. In reality,
2884 this will only ever be just the newline character, but
2885 it doesn't hurt to be precise.
2887 strncpy(bp, cont_seq, cmatch);
2899 buf[buf_len] = NULLCHAR;
2900 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2905 while (i < buf_len) {
2906 /* Deal with part of the TELNET option negotiation
2907 protocol. We refuse to do anything beyond the
2908 defaults, except that we allow the WILL ECHO option,
2909 which ICS uses to turn off password echoing when we are
2910 directly connected to it. We reject this option
2911 if localLineEditing mode is on (always on in xboard)
2912 and we are talking to port 23, which might be a real
2913 telnet server that will try to keep WILL ECHO on permanently.
2915 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2916 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2917 unsigned char option;
2919 switch ((unsigned char) buf[++i]) {
2921 if (appData.debugMode)
2922 fprintf(debugFP, "\n<WILL ");
2923 switch (option = (unsigned char) buf[++i]) {
2925 if (appData.debugMode)
2926 fprintf(debugFP, "ECHO ");
2927 /* Reply only if this is a change, according
2928 to the protocol rules. */
2929 if (remoteEchoOption) break;
2930 if (appData.localLineEditing &&
2931 atoi(appData.icsPort) == TN_PORT) {
2932 TelnetRequest(TN_DONT, TN_ECHO);
2935 TelnetRequest(TN_DO, TN_ECHO);
2936 remoteEchoOption = TRUE;
2940 if (appData.debugMode)
2941 fprintf(debugFP, "%d ", option);
2942 /* Whatever this is, we don't want it. */
2943 TelnetRequest(TN_DONT, option);
2948 if (appData.debugMode)
2949 fprintf(debugFP, "\n<WONT ");
2950 switch (option = (unsigned char) buf[++i]) {
2952 if (appData.debugMode)
2953 fprintf(debugFP, "ECHO ");
2954 /* Reply only if this is a change, according
2955 to the protocol rules. */
2956 if (!remoteEchoOption) break;
2958 TelnetRequest(TN_DONT, TN_ECHO);
2959 remoteEchoOption = FALSE;
2962 if (appData.debugMode)
2963 fprintf(debugFP, "%d ", (unsigned char) option);
2964 /* Whatever this is, it must already be turned
2965 off, because we never agree to turn on
2966 anything non-default, so according to the
2967 protocol rules, we don't reply. */
2972 if (appData.debugMode)
2973 fprintf(debugFP, "\n<DO ");
2974 switch (option = (unsigned char) buf[++i]) {
2976 /* Whatever this is, we refuse to do it. */
2977 if (appData.debugMode)
2978 fprintf(debugFP, "%d ", option);
2979 TelnetRequest(TN_WONT, option);
2984 if (appData.debugMode)
2985 fprintf(debugFP, "\n<DONT ");
2986 switch (option = (unsigned char) buf[++i]) {
2988 if (appData.debugMode)
2989 fprintf(debugFP, "%d ", option);
2990 /* Whatever this is, we are already not doing
2991 it, because we never agree to do anything
2992 non-default, so according to the protocol
2993 rules, we don't reply. */
2998 if (appData.debugMode)
2999 fprintf(debugFP, "\n<IAC ");
3000 /* Doubled IAC; pass it through */
3004 if (appData.debugMode)
3005 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3006 /* Drop all other telnet commands on the floor */
3009 if (oldi > next_out)
3010 SendToPlayer(&buf[next_out], oldi - next_out);
3016 /* OK, this at least will *usually* work */
3017 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3021 if (loggedOn && !intfSet) {
3022 if (ics_type == ICS_ICC) {
3023 snprintf(str, MSG_SIZ,
3024 "/set-quietly interface %s\n/set-quietly style 12\n",
3026 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3027 strcat(str, "/set-2 51 1\n/set seek 1\n");
3028 } else if (ics_type == ICS_CHESSNET) {
3029 snprintf(str, MSG_SIZ, "/style 12\n");
3031 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3032 strcat(str, programVersion);
3033 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3034 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3035 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3037 strcat(str, "$iset nohighlight 1\n");
3039 strcat(str, "$iset lock 1\n$style 12\n");
3042 NotifyFrontendLogin();
3046 if (started == STARTED_COMMENT) {
3047 /* Accumulate characters in comment */
3048 parse[parse_pos++] = buf[i];
3049 if (buf[i] == '\n') {
3050 parse[parse_pos] = NULLCHAR;
3051 if(chattingPartner>=0) {
3053 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3054 OutputChatMessage(chattingPartner, mess);
3055 chattingPartner = -1;
3056 next_out = i+1; // [HGM] suppress printing in ICS window
3058 if(!suppressKibitz) // [HGM] kibitz
3059 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3060 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3061 int nrDigit = 0, nrAlph = 0, j;
3062 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3063 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3064 parse[parse_pos] = NULLCHAR;
3065 // try to be smart: if it does not look like search info, it should go to
3066 // ICS interaction window after all, not to engine-output window.
3067 for(j=0; j<parse_pos; j++) { // count letters and digits
3068 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3069 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3070 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3072 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3073 int depth=0; float score;
3074 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3075 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3076 pvInfoList[forwardMostMove-1].depth = depth;
3077 pvInfoList[forwardMostMove-1].score = 100*score;
3079 OutputKibitz(suppressKibitz, parse);
3082 if(gameMode == IcsObserving) // restore original ICS messages
3083 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3084 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3086 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3087 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3088 SendToPlayer(tmp, strlen(tmp));
3090 next_out = i+1; // [HGM] suppress printing in ICS window
3092 started = STARTED_NONE;
3094 /* Don't match patterns against characters in comment */
3099 if (started == STARTED_CHATTER) {
3100 if (buf[i] != '\n') {
3101 /* Don't match patterns against characters in chatter */
3105 started = STARTED_NONE;
3106 if(suppressKibitz) next_out = i+1;
3109 /* Kludge to deal with rcmd protocol */
3110 if (firstTime && looking_at(buf, &i, "\001*")) {
3111 DisplayFatalError(&buf[1], 0, 1);
3117 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3120 if (appData.debugMode)
3121 fprintf(debugFP, "ics_type %d\n", ics_type);
3124 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3125 ics_type = ICS_FICS;
3127 if (appData.debugMode)
3128 fprintf(debugFP, "ics_type %d\n", ics_type);
3131 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3132 ics_type = ICS_CHESSNET;
3134 if (appData.debugMode)
3135 fprintf(debugFP, "ics_type %d\n", ics_type);
3140 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3141 looking_at(buf, &i, "Logging you in as \"*\"") ||
3142 looking_at(buf, &i, "will be \"*\""))) {
3143 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3147 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3149 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3150 DisplayIcsInteractionTitle(buf);
3151 have_set_title = TRUE;
3154 /* skip finger notes */
3155 if (started == STARTED_NONE &&
3156 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3157 (buf[i] == '1' && buf[i+1] == '0')) &&
3158 buf[i+2] == ':' && buf[i+3] == ' ') {
3159 started = STARTED_CHATTER;
3165 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3166 if(appData.seekGraph) {
3167 if(soughtPending && MatchSoughtLine(buf+i)) {
3168 i = strstr(buf+i, "rated") - buf;
3169 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3170 next_out = leftover_start = i;
3171 started = STARTED_CHATTER;
3172 suppressKibitz = TRUE;
3175 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3176 && looking_at(buf, &i, "* ads displayed")) {
3177 soughtPending = FALSE;
3182 if(appData.autoRefresh) {
3183 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3184 int s = (ics_type == ICS_ICC); // ICC format differs
3186 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3187 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3188 looking_at(buf, &i, "*% "); // eat prompt
3189 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3190 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191 next_out = i; // suppress
3194 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3195 char *p = star_match[0];
3197 if(seekGraphUp) RemoveSeekAd(atoi(p));
3198 while(*p && *p++ != ' '); // next
3200 looking_at(buf, &i, "*% "); // eat prompt
3201 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3208 /* skip formula vars */
3209 if (started == STARTED_NONE &&
3210 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3211 started = STARTED_CHATTER;
3216 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3217 if (appData.autoKibitz && started == STARTED_NONE &&
3218 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3219 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3220 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3221 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3222 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3223 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3224 suppressKibitz = TRUE;
3225 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3227 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3228 && (gameMode == IcsPlayingWhite)) ||
3229 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3230 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3231 started = STARTED_CHATTER; // own kibitz we simply discard
3233 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3234 parse_pos = 0; parse[0] = NULLCHAR;
3235 savingComment = TRUE;
3236 suppressKibitz = gameMode != IcsObserving ? 2 :
3237 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3241 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3242 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3243 && atoi(star_match[0])) {
3244 // suppress the acknowledgements of our own autoKibitz
3246 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3248 SendToPlayer(star_match[0], strlen(star_match[0]));
3249 if(looking_at(buf, &i, "*% ")) // eat prompt
3250 suppressKibitz = FALSE;
3254 } // [HGM] kibitz: end of patch
3256 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3258 // [HGM] chat: intercept tells by users for which we have an open chat window
3260 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3261 looking_at(buf, &i, "* whispers:") ||
3262 looking_at(buf, &i, "* kibitzes:") ||
3263 looking_at(buf, &i, "* shouts:") ||
3264 looking_at(buf, &i, "* c-shouts:") ||
3265 looking_at(buf, &i, "--> * ") ||
3266 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3267 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3268 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3269 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3271 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3272 chattingPartner = -1;
3274 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3275 for(p=0; p<MAX_CHAT; p++) {
3276 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3277 talker[0] = '['; strcat(talker, "] ");
3278 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3279 chattingPartner = p; break;
3282 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3283 for(p=0; p<MAX_CHAT; p++) {
3284 if(!strcmp("kibitzes", chatPartner[p])) {
3285 talker[0] = '['; strcat(talker, "] ");
3286 chattingPartner = p; break;
3289 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3290 for(p=0; p<MAX_CHAT; p++) {
3291 if(!strcmp("whispers", chatPartner[p])) {
3292 talker[0] = '['; strcat(talker, "] ");
3293 chattingPartner = p; break;
3296 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3297 if(buf[i-8] == '-' && buf[i-3] == 't')
3298 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3299 if(!strcmp("c-shouts", chatPartner[p])) {
3300 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3301 chattingPartner = p; break;
3304 if(chattingPartner < 0)
3305 for(p=0; p<MAX_CHAT; p++) {
3306 if(!strcmp("shouts", chatPartner[p])) {
3307 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3308 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3309 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3310 chattingPartner = p; break;
3314 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3315 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3316 talker[0] = 0; Colorize(ColorTell, FALSE);
3317 chattingPartner = p; break;
3319 if(chattingPartner<0) i = oldi; else {
3320 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3321 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3322 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3323 started = STARTED_COMMENT;
3324 parse_pos = 0; parse[0] = NULLCHAR;
3325 savingComment = 3 + chattingPartner; // counts as TRUE
3326 suppressKibitz = TRUE;
3329 } // [HGM] chat: end of patch
3332 if (appData.zippyTalk || appData.zippyPlay) {
3333 /* [DM] Backup address for color zippy lines */
3335 if (loggedOn == TRUE)
3336 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3337 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3339 } // [DM] 'else { ' deleted
3341 /* Regular tells and says */
3342 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3343 looking_at(buf, &i, "* (your partner) tells you: ") ||
3344 looking_at(buf, &i, "* says: ") ||
3345 /* Don't color "message" or "messages" output */
3346 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3347 looking_at(buf, &i, "*. * at *:*: ") ||
3348 looking_at(buf, &i, "--* (*:*): ") ||
3349 /* Message notifications (same color as tells) */
3350 looking_at(buf, &i, "* has left a message ") ||
3351 looking_at(buf, &i, "* just sent you a message:\n") ||
3352 /* Whispers and kibitzes */
3353 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3354 looking_at(buf, &i, "* kibitzes: ") ||
3356 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3358 if (tkind == 1 && strchr(star_match[0], ':')) {
3359 /* Avoid "tells you:" spoofs in channels */
3362 if (star_match[0][0] == NULLCHAR ||
3363 strchr(star_match[0], ' ') ||
3364 (tkind == 3 && strchr(star_match[1], ' '))) {
3365 /* Reject bogus matches */
3368 if (appData.colorize) {
3369 if (oldi > next_out) {
3370 SendToPlayer(&buf[next_out], oldi - next_out);
3375 Colorize(ColorTell, FALSE);
3376 curColor = ColorTell;
3379 Colorize(ColorKibitz, FALSE);
3380 curColor = ColorKibitz;
3383 p = strrchr(star_match[1], '(');
3390 Colorize(ColorChannel1, FALSE);
3391 curColor = ColorChannel1;
3393 Colorize(ColorChannel, FALSE);
3394 curColor = ColorChannel;
3398 curColor = ColorNormal;
3402 if (started == STARTED_NONE && appData.autoComment &&
3403 (gameMode == IcsObserving ||
3404 gameMode == IcsPlayingWhite ||
3405 gameMode == IcsPlayingBlack)) {
3406 parse_pos = i - oldi;
3407 memcpy(parse, &buf[oldi], parse_pos);
3408 parse[parse_pos] = NULLCHAR;
3409 started = STARTED_COMMENT;
3410 savingComment = TRUE;
3412 started = STARTED_CHATTER;
3413 savingComment = FALSE;
3420 if (looking_at(buf, &i, "* s-shouts: ") ||
3421 looking_at(buf, &i, "* c-shouts: ")) {
3422 if (appData.colorize) {
3423 if (oldi > next_out) {
3424 SendToPlayer(&buf[next_out], oldi - next_out);
3427 Colorize(ColorSShout, FALSE);
3428 curColor = ColorSShout;
3431 started = STARTED_CHATTER;
3435 if (looking_at(buf, &i, "--->")) {
3440 if (looking_at(buf, &i, "* shouts: ") ||
3441 looking_at(buf, &i, "--> ")) {
3442 if (appData.colorize) {
3443 if (oldi > next_out) {
3444 SendToPlayer(&buf[next_out], oldi - next_out);
3447 Colorize(ColorShout, FALSE);
3448 curColor = ColorShout;
3451 started = STARTED_CHATTER;
3455 if (looking_at( buf, &i, "Challenge:")) {
3456 if (appData.colorize) {
3457 if (oldi > next_out) {
3458 SendToPlayer(&buf[next_out], oldi - next_out);
3461 Colorize(ColorChallenge, FALSE);
3462 curColor = ColorChallenge;
3468 if (looking_at(buf, &i, "* offers you") ||
3469 looking_at(buf, &i, "* offers to be") ||
3470 looking_at(buf, &i, "* would like to") ||
3471 looking_at(buf, &i, "* requests to") ||
3472 looking_at(buf, &i, "Your opponent offers") ||
3473 looking_at(buf, &i, "Your opponent requests")) {
3475 if (appData.colorize) {
3476 if (oldi > next_out) {
3477 SendToPlayer(&buf[next_out], oldi - next_out);
3480 Colorize(ColorRequest, FALSE);
3481 curColor = ColorRequest;
3486 if (looking_at(buf, &i, "* (*) seeking")) {
3487 if (appData.colorize) {
3488 if (oldi > next_out) {
3489 SendToPlayer(&buf[next_out], oldi - next_out);
3492 Colorize(ColorSeek, FALSE);
3493 curColor = ColorSeek;
3498 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3500 if (looking_at(buf, &i, "\\ ")) {
3501 if (prevColor != ColorNormal) {
3502 if (oldi > next_out) {
3503 SendToPlayer(&buf[next_out], oldi - next_out);
3506 Colorize(prevColor, TRUE);
3507 curColor = prevColor;
3509 if (savingComment) {
3510 parse_pos = i - oldi;
3511 memcpy(parse, &buf[oldi], parse_pos);
3512 parse[parse_pos] = NULLCHAR;
3513 started = STARTED_COMMENT;
3514 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3515 chattingPartner = savingComment - 3; // kludge to remember the box
3517 started = STARTED_CHATTER;
3522 if (looking_at(buf, &i, "Black Strength :") ||
3523 looking_at(buf, &i, "<<< style 10 board >>>") ||
3524 looking_at(buf, &i, "<10>") ||
3525 looking_at(buf, &i, "#@#")) {
3526 /* Wrong board style */
3528 SendToICS(ics_prefix);
3529 SendToICS("set style 12\n");
3530 SendToICS(ics_prefix);
3531 SendToICS("refresh\n");
3535 if (looking_at(buf, &i, "login:")) {
3536 if (!have_sent_ICS_logon) {
3538 have_sent_ICS_logon = 1;
3539 else // no init script was found
3540 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3541 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3542 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3547 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3548 (looking_at(buf, &i, "\n<12> ") ||
3549 looking_at(buf, &i, "<12> "))) {
3551 if (oldi > next_out) {
3552 SendToPlayer(&buf[next_out], oldi - next_out);
3555 started = STARTED_BOARD;
3560 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3561 looking_at(buf, &i, "<b1> ")) {
3562 if (oldi > next_out) {
3563 SendToPlayer(&buf[next_out], oldi - next_out);
3566 started = STARTED_HOLDINGS;
3571 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3573 /* Header for a move list -- first line */
3575 switch (ics_getting_history) {
3579 case BeginningOfGame:
3580 /* User typed "moves" or "oldmoves" while we
3581 were idle. Pretend we asked for these
3582 moves and soak them up so user can step
3583 through them and/or save them.
3586 gameMode = IcsObserving;
3589 ics_getting_history = H_GOT_UNREQ_HEADER;
3591 case EditGame: /*?*/
3592 case EditPosition: /*?*/
3593 /* Should above feature work in these modes too? */
3594 /* For now it doesn't */
3595 ics_getting_history = H_GOT_UNWANTED_HEADER;
3598 ics_getting_history = H_GOT_UNWANTED_HEADER;
3603 /* Is this the right one? */
3604 if (gameInfo.white && gameInfo.black &&
3605 strcmp(gameInfo.white, star_match[0]) == 0 &&
3606 strcmp(gameInfo.black, star_match[2]) == 0) {
3608 ics_getting_history = H_GOT_REQ_HEADER;
3611 case H_GOT_REQ_HEADER:
3612 case H_GOT_UNREQ_HEADER:
3613 case H_GOT_UNWANTED_HEADER:
3614 case H_GETTING_MOVES:
3615 /* Should not happen */
3616 DisplayError(_("Error gathering move list: two headers"), 0);
3617 ics_getting_history = H_FALSE;
3621 /* Save player ratings into gameInfo if needed */
3622 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3623 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3624 (gameInfo.whiteRating == -1 ||
3625 gameInfo.blackRating == -1)) {
3627 gameInfo.whiteRating = string_to_rating(star_match[1]);
3628 gameInfo.blackRating = string_to_rating(star_match[3]);
3629 if (appData.debugMode)
3630 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3631 gameInfo.whiteRating, gameInfo.blackRating);
3636 if (looking_at(buf, &i,
3637 "* * match, initial time: * minute*, increment: * second")) {
3638 /* Header for a move list -- second line */
3639 /* Initial board will follow if this is a wild game */
3640 if (gameInfo.event != NULL) free(gameInfo.event);
3641 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3642 gameInfo.event = StrSave(str);
3643 /* [HGM] we switched variant. Translate boards if needed. */
3644 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3648 if (looking_at(buf, &i, "Move ")) {
3649 /* Beginning of a move list */
3650 switch (ics_getting_history) {
3652 /* Normally should not happen */
3653 /* Maybe user hit reset while we were parsing */
3656 /* Happens if we are ignoring a move list that is not
3657 * the one we just requested. Common if the user
3658 * tries to observe two games without turning off
3661 case H_GETTING_MOVES:
3662 /* Should not happen */
3663 DisplayError(_("Error gathering move list: nested"), 0);
3664 ics_getting_history = H_FALSE;
3666 case H_GOT_REQ_HEADER:
3667 ics_getting_history = H_GETTING_MOVES;
3668 started = STARTED_MOVES;
3670 if (oldi > next_out) {
3671 SendToPlayer(&buf[next_out], oldi - next_out);
3674 case H_GOT_UNREQ_HEADER:
3675 ics_getting_history = H_GETTING_MOVES;
3676 started = STARTED_MOVES_NOHIDE;
3679 case H_GOT_UNWANTED_HEADER:
3680 ics_getting_history = H_FALSE;
3686 if (looking_at(buf, &i, "% ") ||
3687 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3688 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3689 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3690 soughtPending = FALSE;
3694 if(suppressKibitz) next_out = i;
3695 savingComment = FALSE;
3699 case STARTED_MOVES_NOHIDE:
3700 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3701 parse[parse_pos + i - oldi] = NULLCHAR;
3702 ParseGameHistory(parse);
3704 if (appData.zippyPlay && first.initDone) {
3705 FeedMovesToProgram(&first, forwardMostMove);
3706 if (gameMode == IcsPlayingWhite) {
3707 if (WhiteOnMove(forwardMostMove)) {
3708 if (first.sendTime) {
3709 if (first.useColors) {
3710 SendToProgram("black\n", &first);
3712 SendTimeRemaining(&first, TRUE);
3714 if (first.useColors) {
3715 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3717 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3718 first.maybeThinking = TRUE;
3720 if (first.usePlayother) {
3721 if (first.sendTime) {
3722 SendTimeRemaining(&first, TRUE);
3724 SendToProgram("playother\n", &first);
3730 } else if (gameMode == IcsPlayingBlack) {
3731 if (!WhiteOnMove(forwardMostMove)) {
3732 if (first.sendTime) {
3733 if (first.useColors) {
3734 SendToProgram("white\n", &first);
3736 SendTimeRemaining(&first, FALSE);
3738 if (first.useColors) {
3739 SendToProgram("black\n", &first);
3741 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3742 first.maybeThinking = TRUE;
3744 if (first.usePlayother) {
3745 if (first.sendTime) {
3746 SendTimeRemaining(&first, FALSE);
3748 SendToProgram("playother\n", &first);
3757 if (gameMode == IcsObserving && ics_gamenum == -1) {
3758 /* Moves came from oldmoves or moves command
3759 while we weren't doing anything else.
3761 currentMove = forwardMostMove;
3762 ClearHighlights();/*!!could figure this out*/
3763 flipView = appData.flipView;
3764 DrawPosition(TRUE, boards[currentMove]);
3765 DisplayBothClocks();
3766 snprintf(str, MSG_SIZ, "%s %s %s",
3767 gameInfo.white, _("vs."), gameInfo.black);
3771 /* Moves were history of an active game */
3772 if (gameInfo.resultDetails != NULL) {
3773 free(gameInfo.resultDetails);
3774 gameInfo.resultDetails = NULL;
3777 HistorySet(parseList, backwardMostMove,
3778 forwardMostMove, currentMove-1);
3779 DisplayMove(currentMove - 1);
3780 if (started == STARTED_MOVES) next_out = i;
3781 started = STARTED_NONE;
3782 ics_getting_history = H_FALSE;
3785 case STARTED_OBSERVE:
3786 started = STARTED_NONE;
3787 SendToICS(ics_prefix);
3788 SendToICS("refresh\n");
3794 if(bookHit) { // [HGM] book: simulate book reply
3795 static char bookMove[MSG_SIZ]; // a bit generous?
3797 programStats.nodes = programStats.depth = programStats.time =
3798 programStats.score = programStats.got_only_move = 0;
3799 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3801 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3802 strcat(bookMove, bookHit);
3803 HandleMachineMove(bookMove, &first);
3808 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3809 started == STARTED_HOLDINGS ||
3810 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3811 /* Accumulate characters in move list or board */
3812 parse[parse_pos++] = buf[i];
3815 /* Start of game messages. Mostly we detect start of game
3816 when the first board image arrives. On some versions
3817 of the ICS, though, we need to do a "refresh" after starting
3818 to observe in order to get the current board right away. */
3819 if (looking_at(buf, &i, "Adding game * to observation list")) {
3820 started = STARTED_OBSERVE;
3824 /* Handle auto-observe */
3825 if (appData.autoObserve &&
3826 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3827 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3829 /* Choose the player that was highlighted, if any. */
3830 if (star_match[0][0] == '\033' ||
3831 star_match[1][0] != '\033') {
3832 player = star_match[0];
3834 player = star_match[2];
3836 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3837 ics_prefix, StripHighlightAndTitle(player));
3840 /* Save ratings from notify string */
3841 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3842 player1Rating = string_to_rating(star_match[1]);
3843 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3844 player2Rating = string_to_rating(star_match[3]);
3846 if (appData.debugMode)
3848 "Ratings from 'Game notification:' %s %d, %s %d\n",
3849 player1Name, player1Rating,
3850 player2Name, player2Rating);
3855 /* Deal with automatic examine mode after a game,
3856 and with IcsObserving -> IcsExamining transition */
3857 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3858 looking_at(buf, &i, "has made you an examiner of game *")) {
3860 int gamenum = atoi(star_match[0]);
3861 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3862 gamenum == ics_gamenum) {
3863 /* We were already playing or observing this game;
3864 no need to refetch history */
3865 gameMode = IcsExamining;
3867 pauseExamForwardMostMove = forwardMostMove;
3868 } else if (currentMove < forwardMostMove) {
3869 ForwardInner(forwardMostMove);