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 }
622 ChessSquare GothicArray[2][BOARD_FILES] = {
623 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
624 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
625 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
626 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
629 #define GothicArray CapablancaArray
633 ChessSquare FalconArray[2][BOARD_FILES] = {
634 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
635 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
636 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
637 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
640 #define FalconArray CapablancaArray
643 #else // !(BOARD_FILES>=10)
644 #define XiangqiPosition FIDEArray
645 #define CapablancaArray FIDEArray
646 #define GothicArray FIDEArray
647 #define GreatArray FIDEArray
648 #endif // !(BOARD_FILES>=10)
650 #if (BOARD_FILES>=12)
651 ChessSquare CourierArray[2][BOARD_FILES] = {
652 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
653 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
654 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
655 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
657 ChessSquare ChuArray[6][BOARD_FILES] = {
658 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
659 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
660 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
661 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
662 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
663 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
664 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
665 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
666 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
667 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
668 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
669 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
671 #else // !(BOARD_FILES>=12)
672 #define CourierArray CapablancaArray
673 #define ChuArray CapablancaArray
674 #endif // !(BOARD_FILES>=12)
677 Board initialPosition;
680 /* Convert str to a rating. Checks for special cases of "----",
682 "++++", etc. Also strips ()'s */
684 string_to_rating (char *str)
686 while(*str && !isdigit(*str)) ++str;
688 return 0; /* One of the special "no rating" cases */
696 /* Init programStats */
697 programStats.movelist[0] = 0;
698 programStats.depth = 0;
699 programStats.nr_moves = 0;
700 programStats.moves_left = 0;
701 programStats.nodes = 0;
702 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
703 programStats.score = 0;
704 programStats.got_only_move = 0;
705 programStats.got_fail = 0;
706 programStats.line_is_book = 0;
711 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
712 if (appData.firstPlaysBlack) {
713 first.twoMachinesColor = "black\n";
714 second.twoMachinesColor = "white\n";
716 first.twoMachinesColor = "white\n";
717 second.twoMachinesColor = "black\n";
720 first.other = &second;
721 second.other = &first;
724 if(appData.timeOddsMode) {
725 norm = appData.timeOdds[0];
726 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
728 first.timeOdds = appData.timeOdds[0]/norm;
729 second.timeOdds = appData.timeOdds[1]/norm;
732 if(programVersion) free(programVersion);
733 if (appData.noChessProgram) {
734 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
735 sprintf(programVersion, "%s", PACKAGE_STRING);
737 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
738 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
739 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
744 UnloadEngine (ChessProgramState *cps)
746 /* Kill off first chess program */
747 if (cps->isr != NULL)
748 RemoveInputSource(cps->isr);
751 if (cps->pr != NoProc) {
753 DoSleep( appData.delayBeforeQuit );
754 SendToProgram("quit\n", cps);
755 DoSleep( appData.delayAfterQuit );
756 DestroyChildProcess(cps->pr, cps->useSigterm);
759 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
763 ClearOptions (ChessProgramState *cps)
766 cps->nrOptions = cps->comboCnt = 0;
767 for(i=0; i<MAX_OPTIONS; i++) {
768 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
769 cps->option[i].textValue = 0;
773 char *engineNames[] = {
774 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
775 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
777 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
778 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
783 InitEngine (ChessProgramState *cps, int n)
784 { // [HGM] all engine initialiation put in a function that does one engine
788 cps->which = engineNames[n];
789 cps->maybeThinking = FALSE;
793 cps->sendDrawOffers = 1;
795 cps->program = appData.chessProgram[n];
796 cps->host = appData.host[n];
797 cps->dir = appData.directory[n];
798 cps->initString = appData.engInitString[n];
799 cps->computerString = appData.computerString[n];
800 cps->useSigint = TRUE;
801 cps->useSigterm = TRUE;
802 cps->reuse = appData.reuse[n];
803 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
804 cps->useSetboard = FALSE;
806 cps->usePing = FALSE;
809 cps->usePlayother = FALSE;
810 cps->useColors = TRUE;
811 cps->useUsermove = FALSE;
812 cps->sendICS = FALSE;
813 cps->sendName = appData.icsActive;
814 cps->sdKludge = FALSE;
815 cps->stKludge = FALSE;
816 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
817 TidyProgramName(cps->program, cps->host, cps->tidy);
819 ASSIGN(cps->variants, appData.variant);
820 cps->analysisSupport = 2; /* detect */
821 cps->analyzing = FALSE;
822 cps->initDone = FALSE;
825 /* New features added by Tord: */
826 cps->useFEN960 = FALSE;
827 cps->useOOCastle = TRUE;
828 /* End of new features added by Tord. */
829 cps->fenOverride = appData.fenOverride[n];
831 /* [HGM] time odds: set factor for each machine */
832 cps->timeOdds = appData.timeOdds[n];
834 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
835 cps->accumulateTC = appData.accumulateTC[n];
836 cps->maxNrOfSessions = 1;
841 cps->supportsNPS = UNKNOWN;
842 cps->memSize = FALSE;
843 cps->maxCores = FALSE;
844 ASSIGN(cps->egtFormats, "");
847 cps->optionSettings = appData.engOptions[n];
849 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
850 cps->isUCI = appData.isUCI[n]; /* [AS] */
851 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
854 if (appData.protocolVersion[n] > PROTOVER
855 || appData.protocolVersion[n] < 1)
860 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
861 appData.protocolVersion[n]);
862 if( (len >= MSG_SIZ) && appData.debugMode )
863 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
865 DisplayFatalError(buf, 0, 2);
869 cps->protocolVersion = appData.protocolVersion[n];
872 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
873 ParseFeatures(appData.featureDefaults, cps);
876 ChessProgramState *savCps;
884 if(WaitForEngine(savCps, LoadEngine)) return;
885 CommonEngineInit(); // recalculate time odds
886 if(gameInfo.variant != StringToVariant(appData.variant)) {
887 // we changed variant when loading the engine; this forces us to reset
888 Reset(TRUE, savCps != &first);
889 oldMode = BeginningOfGame; // to prevent restoring old mode
891 InitChessProgram(savCps, FALSE);
892 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
893 DisplayMessage("", "");
894 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
895 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
898 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
902 ReplaceEngine (ChessProgramState *cps, int n)
904 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
906 if(oldMode != BeginningOfGame) EditGameEvent();
909 appData.noChessProgram = FALSE;
910 appData.clockMode = TRUE;
913 if(n) return; // only startup first engine immediately; second can wait
914 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
921 static char resetOptions[] =
922 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
923 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
924 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
925 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
928 FloatToFront(char **list, char *engineLine)
930 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
932 if(appData.recentEngines <= 0) return;
933 TidyProgramName(engineLine, "localhost", tidy+1);
934 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
935 strncpy(buf+1, *list, MSG_SIZ-50);
936 if(p = strstr(buf, tidy)) { // tidy name appears in list
937 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
938 while(*p++ = *++q); // squeeze out
940 strcat(tidy, buf+1); // put list behind tidy name
941 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
942 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
943 ASSIGN(*list, tidy+1);
946 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
949 Load (ChessProgramState *cps, int i)
951 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
952 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
953 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
954 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
955 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
956 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
957 appData.firstProtocolVersion = PROTOVER;
958 ParseArgsFromString(buf);
960 ReplaceEngine(cps, i);
961 FloatToFront(&appData.recentEngineList, engineLine);
965 while(q = strchr(p, SLASH)) p = q+1;
966 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
967 if(engineDir[0] != NULLCHAR) {
968 ASSIGN(appData.directory[i], engineDir); p = engineName;
969 } else if(p != engineName) { // derive directory from engine path, when not given
971 ASSIGN(appData.directory[i], engineName);
973 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
974 } else { ASSIGN(appData.directory[i], "."); }
975 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
977 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
978 snprintf(command, MSG_SIZ, "%s %s", p, params);
981 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
982 ASSIGN(appData.chessProgram[i], p);
983 appData.isUCI[i] = isUCI;
984 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
985 appData.hasOwnBookUCI[i] = hasBook;
986 if(!nickName[0]) useNick = FALSE;
987 if(useNick) ASSIGN(appData.pgnName[i], nickName);
991 q = firstChessProgramNames;
992 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
993 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
994 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
995 quote, p, quote, appData.directory[i],
996 useNick ? " -fn \"" : "",
997 useNick ? nickName : "",
999 v1 ? " -firstProtocolVersion 1" : "",
1000 hasBook ? "" : " -fNoOwnBookUCI",
1001 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1002 storeVariant ? " -variant " : "",
1003 storeVariant ? VariantName(gameInfo.variant) : "");
1004 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1005 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1006 if(insert != q) insert[-1] = NULLCHAR;
1007 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1009 FloatToFront(&appData.recentEngineList, buf);
1011 ReplaceEngine(cps, i);
1017 int matched, min, sec;
1019 * Parse timeControl resource
1021 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1022 appData.movesPerSession)) {
1024 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1025 DisplayFatalError(buf, 0, 2);
1029 * Parse searchTime resource
1031 if (*appData.searchTime != NULLCHAR) {
1032 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1034 searchTime = min * 60;
1035 } else if (matched == 2) {
1036 searchTime = min * 60 + sec;
1039 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1040 DisplayFatalError(buf, 0, 2);
1049 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1050 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1052 GetTimeMark(&programStartTime);
1053 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1054 appData.seedBase = random() + (random()<<15);
1055 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1057 ClearProgramStats();
1058 programStats.ok_to_send = 1;
1059 programStats.seen_stat = 0;
1062 * Initialize game list
1068 * Internet chess server status
1070 if (appData.icsActive) {
1071 appData.matchMode = FALSE;
1072 appData.matchGames = 0;
1074 appData.noChessProgram = !appData.zippyPlay;
1076 appData.zippyPlay = FALSE;
1077 appData.zippyTalk = FALSE;
1078 appData.noChessProgram = TRUE;
1080 if (*appData.icsHelper != NULLCHAR) {
1081 appData.useTelnet = TRUE;
1082 appData.telnetProgram = appData.icsHelper;
1085 appData.zippyTalk = appData.zippyPlay = FALSE;
1088 /* [AS] Initialize pv info list [HGM] and game state */
1092 for( i=0; i<=framePtr; i++ ) {
1093 pvInfoList[i].depth = -1;
1094 boards[i][EP_STATUS] = EP_NONE;
1095 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1101 /* [AS] Adjudication threshold */
1102 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1104 InitEngine(&first, 0);
1105 InitEngine(&second, 1);
1108 pairing.which = "pairing"; // pairing engine
1109 pairing.pr = NoProc;
1111 pairing.program = appData.pairingEngine;
1112 pairing.host = "localhost";
1115 if (appData.icsActive) {
1116 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1117 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1118 appData.clockMode = FALSE;
1119 first.sendTime = second.sendTime = 0;
1123 /* Override some settings from environment variables, for backward
1124 compatibility. Unfortunately it's not feasible to have the env
1125 vars just set defaults, at least in xboard. Ugh.
1127 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1132 if (!appData.icsActive) {
1136 /* Check for variants that are supported only in ICS mode,
1137 or not at all. Some that are accepted here nevertheless
1138 have bugs; see comments below.
1140 VariantClass variant = StringToVariant(appData.variant);
1142 case VariantBughouse: /* need four players and two boards */
1143 case VariantKriegspiel: /* need to hide pieces and move details */
1144 /* case VariantFischeRandom: (Fabien: moved below) */
1145 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1146 if( (len >= MSG_SIZ) && appData.debugMode )
1147 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1149 DisplayFatalError(buf, 0, 2);
1152 case VariantUnknown:
1153 case VariantLoadable:
1163 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1164 if( (len >= MSG_SIZ) && appData.debugMode )
1165 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1167 DisplayFatalError(buf, 0, 2);
1170 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1171 case VariantFairy: /* [HGM] TestLegality definitely off! */
1172 case VariantGothic: /* [HGM] should work */
1173 case VariantCapablanca: /* [HGM] should work */
1174 case VariantCourier: /* [HGM] initial forced moves not implemented */
1175 case VariantShogi: /* [HGM] could still mate with pawn drop */
1176 case VariantChu: /* [HGM] experimental */
1177 case VariantKnightmate: /* [HGM] should work */
1178 case VariantCylinder: /* [HGM] untested */
1179 case VariantFalcon: /* [HGM] untested */
1180 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1181 offboard interposition not understood */
1182 case VariantNormal: /* definitely works! */
1183 case VariantWildCastle: /* pieces not automatically shuffled */
1184 case VariantNoCastle: /* pieces not automatically shuffled */
1185 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1186 case VariantLosers: /* should work except for win condition,
1187 and doesn't know captures are mandatory */
1188 case VariantSuicide: /* should work except for win condition,
1189 and doesn't know captures are mandatory */
1190 case VariantGiveaway: /* should work except for win condition,
1191 and doesn't know captures are mandatory */
1192 case VariantTwoKings: /* should work */
1193 case VariantAtomic: /* should work except for win condition */
1194 case Variant3Check: /* should work except for win condition */
1195 case VariantShatranj: /* should work except for all win conditions */
1196 case VariantMakruk: /* should work except for draw countdown */
1197 case VariantASEAN : /* should work except for draw countdown */
1198 case VariantBerolina: /* might work if TestLegality is off */
1199 case VariantCapaRandom: /* should work */
1200 case VariantJanus: /* should work */
1201 case VariantSuper: /* experimental */
1202 case VariantGreat: /* experimental, requires legality testing to be off */
1203 case VariantSChess: /* S-Chess, should work */
1204 case VariantGrand: /* should work */
1205 case VariantSpartan: /* should work */
1206 case VariantLion: /* should work */
1214 NextIntegerFromString (char ** str, long * value)
1219 while( *s == ' ' || *s == '\t' ) {
1225 if( *s >= '0' && *s <= '9' ) {
1226 while( *s >= '0' && *s <= '9' ) {
1227 *value = *value * 10 + (*s - '0');
1240 NextTimeControlFromString (char ** str, long * value)
1243 int result = NextIntegerFromString( str, &temp );
1246 *value = temp * 60; /* Minutes */
1247 if( **str == ':' ) {
1249 result = NextIntegerFromString( str, &temp );
1250 *value += temp; /* Seconds */
1258 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1259 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1260 int result = -1, type = 0; long temp, temp2;
1262 if(**str != ':') return -1; // old params remain in force!
1264 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1265 if( NextIntegerFromString( str, &temp ) ) return -1;
1266 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1269 /* time only: incremental or sudden-death time control */
1270 if(**str == '+') { /* increment follows; read it */
1272 if(**str == '!') type = *(*str)++; // Bronstein TC
1273 if(result = NextIntegerFromString( str, &temp2)) return -1;
1274 *inc = temp2 * 1000;
1275 if(**str == '.') { // read fraction of increment
1276 char *start = ++(*str);
1277 if(result = NextIntegerFromString( str, &temp2)) return -1;
1279 while(start++ < *str) temp2 /= 10;
1283 *moves = 0; *tc = temp * 1000; *incType = type;
1287 (*str)++; /* classical time control */
1288 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1300 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1301 { /* [HGM] get time to add from the multi-session time-control string */
1302 int incType, moves=1; /* kludge to force reading of first session */
1303 long time, increment;
1306 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1308 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1309 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1310 if(movenr == -1) return time; /* last move before new session */
1311 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1312 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1313 if(!moves) return increment; /* current session is incremental */
1314 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1315 } while(movenr >= -1); /* try again for next session */
1317 return 0; // no new time quota on this move
1321 ParseTimeControl (char *tc, float ti, int mps)
1325 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1328 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1329 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1330 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1334 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1336 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1339 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1341 snprintf(buf, MSG_SIZ, ":%s", mytc);
1343 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1345 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1350 /* Parse second time control */
1353 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1361 timeControl_2 = tc2 * 1000;
1371 timeControl = tc1 * 1000;
1374 timeIncrement = ti * 1000; /* convert to ms */
1375 movesPerSession = 0;
1378 movesPerSession = mps;
1386 if (appData.debugMode) {
1387 # ifdef __GIT_VERSION
1388 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1390 fprintf(debugFP, "Version: %s\n", programVersion);
1393 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1395 set_cont_sequence(appData.wrapContSeq);
1396 if (appData.matchGames > 0) {
1397 appData.matchMode = TRUE;
1398 } else if (appData.matchMode) {
1399 appData.matchGames = 1;
1401 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1402 appData.matchGames = appData.sameColorGames;
1403 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1404 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1405 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1408 if (appData.noChessProgram || first.protocolVersion == 1) {
1411 /* kludge: allow timeout for initial "feature" commands */
1413 DisplayMessage("", _("Starting chess program"));
1414 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1419 CalculateIndex (int index, int gameNr)
1420 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1422 if(index > 0) return index; // fixed nmber
1423 if(index == 0) return 1;
1424 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1425 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1430 LoadGameOrPosition (int gameNr)
1431 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1432 if (*appData.loadGameFile != NULLCHAR) {
1433 if (!LoadGameFromFile(appData.loadGameFile,
1434 CalculateIndex(appData.loadGameIndex, gameNr),
1435 appData.loadGameFile, FALSE)) {
1436 DisplayFatalError(_("Bad game file"), 0, 1);
1439 } else if (*appData.loadPositionFile != NULLCHAR) {
1440 if (!LoadPositionFromFile(appData.loadPositionFile,
1441 CalculateIndex(appData.loadPositionIndex, gameNr),
1442 appData.loadPositionFile)) {
1443 DisplayFatalError(_("Bad position file"), 0, 1);
1451 ReserveGame (int gameNr, char resChar)
1453 FILE *tf = fopen(appData.tourneyFile, "r+");
1454 char *p, *q, c, buf[MSG_SIZ];
1455 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1456 safeStrCpy(buf, lastMsg, MSG_SIZ);
1457 DisplayMessage(_("Pick new game"), "");
1458 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1459 ParseArgsFromFile(tf);
1460 p = q = appData.results;
1461 if(appData.debugMode) {
1462 char *r = appData.participants;
1463 fprintf(debugFP, "results = '%s'\n", p);
1464 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1465 fprintf(debugFP, "\n");
1467 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1469 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1470 safeStrCpy(q, p, strlen(p) + 2);
1471 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1472 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1473 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1474 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1477 fseek(tf, -(strlen(p)+4), SEEK_END);
1479 if(c != '"') // depending on DOS or Unix line endings we can be one off
1480 fseek(tf, -(strlen(p)+2), SEEK_END);
1481 else fseek(tf, -(strlen(p)+3), SEEK_END);
1482 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1483 DisplayMessage(buf, "");
1484 free(p); appData.results = q;
1485 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1486 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1487 int round = appData.defaultMatchGames * appData.tourneyType;
1488 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1489 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1490 UnloadEngine(&first); // next game belongs to other pairing;
1491 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1493 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1497 MatchEvent (int mode)
1498 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1500 if(matchMode) { // already in match mode: switch it off
1502 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1505 // if(gameMode != BeginningOfGame) {
1506 // DisplayError(_("You can only start a match from the initial position."), 0);
1510 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1511 /* Set up machine vs. machine match */
1513 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1514 if(appData.tourneyFile[0]) {
1516 if(nextGame > appData.matchGames) {
1518 if(strchr(appData.results, '*') == NULL) {
1520 appData.tourneyCycles++;
1521 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1523 NextTourneyGame(-1, &dummy);
1525 if(nextGame <= appData.matchGames) {
1526 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1528 ScheduleDelayedEvent(NextMatchGame, 10000);
1533 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1534 DisplayError(buf, 0);
1535 appData.tourneyFile[0] = 0;
1539 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1540 DisplayFatalError(_("Can't have a match with no chess programs"),
1545 matchGame = roundNr = 1;
1546 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1550 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1553 InitBackEnd3 P((void))
1555 GameMode initialMode;
1559 InitChessProgram(&first, startedFromSetupPosition);
1561 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1562 free(programVersion);
1563 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1564 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1565 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1568 if (appData.icsActive) {
1570 /* [DM] Make a console window if needed [HGM] merged ifs */
1576 if (*appData.icsCommPort != NULLCHAR)
1577 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1578 appData.icsCommPort);
1580 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1581 appData.icsHost, appData.icsPort);
1583 if( (len >= MSG_SIZ) && appData.debugMode )
1584 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586 DisplayFatalError(buf, err, 1);
1591 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1593 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1594 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1595 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1596 } else if (appData.noChessProgram) {
1602 if (*appData.cmailGameName != NULLCHAR) {
1604 OpenLoopback(&cmailPR);
1606 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1610 DisplayMessage("", "");
1611 if (StrCaseCmp(appData.initialMode, "") == 0) {
1612 initialMode = BeginningOfGame;
1613 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1614 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1615 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1616 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1619 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1620 initialMode = TwoMachinesPlay;
1621 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1622 initialMode = AnalyzeFile;
1623 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1624 initialMode = AnalyzeMode;
1625 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1626 initialMode = MachinePlaysWhite;
1627 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1628 initialMode = MachinePlaysBlack;
1629 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1630 initialMode = EditGame;
1631 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1632 initialMode = EditPosition;
1633 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1634 initialMode = Training;
1636 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1637 if( (len >= MSG_SIZ) && appData.debugMode )
1638 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1640 DisplayFatalError(buf, 0, 2);
1644 if (appData.matchMode) {
1645 if(appData.tourneyFile[0]) { // start tourney from command line
1647 if(f = fopen(appData.tourneyFile, "r")) {
1648 ParseArgsFromFile(f); // make sure tourney parmeters re known
1650 appData.clockMode = TRUE;
1652 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1655 } else if (*appData.cmailGameName != NULLCHAR) {
1656 /* Set up cmail mode */
1657 ReloadCmailMsgEvent(TRUE);
1659 /* Set up other modes */
1660 if (initialMode == AnalyzeFile) {
1661 if (*appData.loadGameFile == NULLCHAR) {
1662 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1666 if (*appData.loadGameFile != NULLCHAR) {
1667 (void) LoadGameFromFile(appData.loadGameFile,
1668 appData.loadGameIndex,
1669 appData.loadGameFile, TRUE);
1670 } else if (*appData.loadPositionFile != NULLCHAR) {
1671 (void) LoadPositionFromFile(appData.loadPositionFile,
1672 appData.loadPositionIndex,
1673 appData.loadPositionFile);
1674 /* [HGM] try to make self-starting even after FEN load */
1675 /* to allow automatic setup of fairy variants with wtm */
1676 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1677 gameMode = BeginningOfGame;
1678 setboardSpoiledMachineBlack = 1;
1680 /* [HGM] loadPos: make that every new game uses the setup */
1681 /* from file as long as we do not switch variant */
1682 if(!blackPlaysFirst) {
1683 startedFromPositionFile = TRUE;
1684 CopyBoard(filePosition, boards[0]);
1687 if (initialMode == AnalyzeMode) {
1688 if (appData.noChessProgram) {
1689 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1692 if (appData.icsActive) {
1693 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1697 } else if (initialMode == AnalyzeFile) {
1698 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1699 ShowThinkingEvent();
1701 AnalysisPeriodicEvent(1);
1702 } else if (initialMode == MachinePlaysWhite) {
1703 if (appData.noChessProgram) {
1704 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1708 if (appData.icsActive) {
1709 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1713 MachineWhiteEvent();
1714 } else if (initialMode == MachinePlaysBlack) {
1715 if (appData.noChessProgram) {
1716 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1720 if (appData.icsActive) {
1721 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1725 MachineBlackEvent();
1726 } else if (initialMode == TwoMachinesPlay) {
1727 if (appData.noChessProgram) {
1728 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1732 if (appData.icsActive) {
1733 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1738 } else if (initialMode == EditGame) {
1740 } else if (initialMode == EditPosition) {
1741 EditPositionEvent();
1742 } else if (initialMode == Training) {
1743 if (*appData.loadGameFile == NULLCHAR) {
1744 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1753 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1755 DisplayBook(current+1);
1757 MoveHistorySet( movelist, first, last, current, pvInfoList );
1759 EvalGraphSet( first, last, current, pvInfoList );
1761 MakeEngineOutputTitle();
1765 * Establish will establish a contact to a remote host.port.
1766 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1767 * used to talk to the host.
1768 * Returns 0 if okay, error code if not.
1775 if (*appData.icsCommPort != NULLCHAR) {
1776 /* Talk to the host through a serial comm port */
1777 return OpenCommPort(appData.icsCommPort, &icsPR);
1779 } else if (*appData.gateway != NULLCHAR) {
1780 if (*appData.remoteShell == NULLCHAR) {
1781 /* Use the rcmd protocol to run telnet program on a gateway host */
1782 snprintf(buf, sizeof(buf), "%s %s %s",
1783 appData.telnetProgram, appData.icsHost, appData.icsPort);
1784 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1787 /* Use the rsh program to run telnet program on a gateway host */
1788 if (*appData.remoteUser == NULLCHAR) {
1789 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1790 appData.gateway, appData.telnetProgram,
1791 appData.icsHost, appData.icsPort);
1793 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1794 appData.remoteShell, appData.gateway,
1795 appData.remoteUser, appData.telnetProgram,
1796 appData.icsHost, appData.icsPort);
1798 return StartChildProcess(buf, "", &icsPR);
1801 } else if (appData.useTelnet) {
1802 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1805 /* TCP socket interface differs somewhat between
1806 Unix and NT; handle details in the front end.
1808 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1813 EscapeExpand (char *p, char *q)
1814 { // [HGM] initstring: routine to shape up string arguments
1815 while(*p++ = *q++) if(p[-1] == '\\')
1817 case 'n': p[-1] = '\n'; break;
1818 case 'r': p[-1] = '\r'; break;
1819 case 't': p[-1] = '\t'; break;
1820 case '\\': p[-1] = '\\'; break;
1821 case 0: *p = 0; return;
1822 default: p[-1] = q[-1]; break;
1827 show_bytes (FILE *fp, char *buf, int count)
1830 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1831 fprintf(fp, "\\%03o", *buf & 0xff);
1840 /* Returns an errno value */
1842 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1844 char buf[8192], *p, *q, *buflim;
1845 int left, newcount, outcount;
1847 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1848 *appData.gateway != NULLCHAR) {
1849 if (appData.debugMode) {
1850 fprintf(debugFP, ">ICS: ");
1851 show_bytes(debugFP, message, count);
1852 fprintf(debugFP, "\n");
1854 return OutputToProcess(pr, message, count, outError);
1857 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1864 if (appData.debugMode) {
1865 fprintf(debugFP, ">ICS: ");
1866 show_bytes(debugFP, buf, newcount);
1867 fprintf(debugFP, "\n");
1869 outcount = OutputToProcess(pr, buf, newcount, outError);
1870 if (outcount < newcount) return -1; /* to be sure */
1877 } else if (((unsigned char) *p) == TN_IAC) {
1878 *q++ = (char) TN_IAC;
1885 if (appData.debugMode) {
1886 fprintf(debugFP, ">ICS: ");
1887 show_bytes(debugFP, buf, newcount);
1888 fprintf(debugFP, "\n");
1890 outcount = OutputToProcess(pr, buf, newcount, outError);
1891 if (outcount < newcount) return -1; /* to be sure */
1896 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1898 int outError, outCount;
1899 static int gotEof = 0;
1902 /* Pass data read from player on to ICS */
1905 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1906 if (outCount < count) {
1907 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1909 if(have_sent_ICS_logon == 2) {
1910 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1911 fprintf(ini, "%s", message);
1912 have_sent_ICS_logon = 3;
1914 have_sent_ICS_logon = 1;
1915 } else if(have_sent_ICS_logon == 3) {
1916 fprintf(ini, "%s", message);
1918 have_sent_ICS_logon = 1;
1920 } else if (count < 0) {
1921 RemoveInputSource(isr);
1922 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1923 } else if (gotEof++ > 0) {
1924 RemoveInputSource(isr);
1925 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1931 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1932 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1933 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1934 SendToICS("date\n");
1935 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1938 /* added routine for printf style output to ics */
1940 ics_printf (char *format, ...)
1942 char buffer[MSG_SIZ];
1945 va_start(args, format);
1946 vsnprintf(buffer, sizeof(buffer), format, args);
1947 buffer[sizeof(buffer)-1] = '\0';
1955 int count, outCount, outError;
1957 if (icsPR == NoProc) return;
1960 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1961 if (outCount < count) {
1962 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1966 /* This is used for sending logon scripts to the ICS. Sending
1967 without a delay causes problems when using timestamp on ICC
1968 (at least on my machine). */
1970 SendToICSDelayed (char *s, long msdelay)
1972 int count, outCount, outError;
1974 if (icsPR == NoProc) return;
1977 if (appData.debugMode) {
1978 fprintf(debugFP, ">ICS: ");
1979 show_bytes(debugFP, s, count);
1980 fprintf(debugFP, "\n");
1982 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1984 if (outCount < count) {
1985 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1990 /* Remove all highlighting escape sequences in s
1991 Also deletes any suffix starting with '('
1994 StripHighlightAndTitle (char *s)
1996 static char retbuf[MSG_SIZ];
1999 while (*s != NULLCHAR) {
2000 while (*s == '\033') {
2001 while (*s != NULLCHAR && !isalpha(*s)) s++;
2002 if (*s != NULLCHAR) s++;
2004 while (*s != NULLCHAR && *s != '\033') {
2005 if (*s == '(' || *s == '[') {
2016 /* Remove all highlighting escape sequences in s */
2018 StripHighlight (char *s)
2020 static char retbuf[MSG_SIZ];
2023 while (*s != NULLCHAR) {
2024 while (*s == '\033') {
2025 while (*s != NULLCHAR && !isalpha(*s)) s++;
2026 if (*s != NULLCHAR) s++;
2028 while (*s != NULLCHAR && *s != '\033') {
2036 char engineVariant[MSG_SIZ];
2037 char *variantNames[] = VARIANT_NAMES;
2039 VariantName (VariantClass v)
2041 if(v == VariantUnknown || *engineVariant) return engineVariant;
2042 return variantNames[v];
2046 /* Identify a variant from the strings the chess servers use or the
2047 PGN Variant tag names we use. */
2049 StringToVariant (char *e)
2053 VariantClass v = VariantNormal;
2054 int i, found = FALSE;
2060 /* [HGM] skip over optional board-size prefixes */
2061 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2062 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2063 while( *e++ != '_');
2066 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2070 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2071 if (StrCaseStr(e, variantNames[i])) {
2072 v = (VariantClass) i;
2079 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2080 || StrCaseStr(e, "wild/fr")
2081 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2082 v = VariantFischeRandom;
2083 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2084 (i = 1, p = StrCaseStr(e, "w"))) {
2086 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2093 case 0: /* FICS only, actually */
2095 /* Castling legal even if K starts on d-file */
2096 v = VariantWildCastle;
2101 /* Castling illegal even if K & R happen to start in
2102 normal positions. */
2103 v = VariantNoCastle;
2116 /* Castling legal iff K & R start in normal positions */
2122 /* Special wilds for position setup; unclear what to do here */
2123 v = VariantLoadable;
2126 /* Bizarre ICC game */
2127 v = VariantTwoKings;
2130 v = VariantKriegspiel;
2136 v = VariantFischeRandom;
2139 v = VariantCrazyhouse;
2142 v = VariantBughouse;
2148 /* Not quite the same as FICS suicide! */
2149 v = VariantGiveaway;
2155 v = VariantShatranj;
2158 /* Temporary names for future ICC types. The name *will* change in
2159 the next xboard/WinBoard release after ICC defines it. */
2197 v = VariantCapablanca;
2200 v = VariantKnightmate;
2206 v = VariantCylinder;
2212 v = VariantCapaRandom;
2215 v = VariantBerolina;
2227 /* Found "wild" or "w" in the string but no number;
2228 must assume it's normal chess. */
2232 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2233 if( (len >= MSG_SIZ) && appData.debugMode )
2234 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2236 DisplayError(buf, 0);
2242 if (appData.debugMode) {
2243 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2244 e, wnum, VariantName(v));
2249 static int leftover_start = 0, leftover_len = 0;
2250 char star_match[STAR_MATCH_N][MSG_SIZ];
2252 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2253 advance *index beyond it, and set leftover_start to the new value of
2254 *index; else return FALSE. If pattern contains the character '*', it
2255 matches any sequence of characters not containing '\r', '\n', or the
2256 character following the '*' (if any), and the matched sequence(s) are
2257 copied into star_match.
2260 looking_at ( char *buf, int *index, char *pattern)
2262 char *bufp = &buf[*index], *patternp = pattern;
2264 char *matchp = star_match[0];
2267 if (*patternp == NULLCHAR) {
2268 *index = leftover_start = bufp - buf;
2272 if (*bufp == NULLCHAR) return FALSE;
2273 if (*patternp == '*') {
2274 if (*bufp == *(patternp + 1)) {
2276 matchp = star_match[++star_count];
2280 } else if (*bufp == '\n' || *bufp == '\r') {
2282 if (*patternp == NULLCHAR)
2287 *matchp++ = *bufp++;
2291 if (*patternp != *bufp) return FALSE;
2298 SendToPlayer (char *data, int length)
2300 int error, outCount;
2301 outCount = OutputToProcess(NoProc, data, length, &error);
2302 if (outCount < length) {
2303 DisplayFatalError(_("Error writing to display"), error, 1);
2308 PackHolding (char packed[], char *holding)
2318 switch (runlength) {
2329 sprintf(q, "%d", runlength);
2341 /* Telnet protocol requests from the front end */
2343 TelnetRequest (unsigned char ddww, unsigned char option)
2345 unsigned char msg[3];
2346 int outCount, outError;
2348 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2350 if (appData.debugMode) {
2351 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2367 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2376 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2379 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2384 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2386 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2393 if (!appData.icsActive) return;
2394 TelnetRequest(TN_DO, TN_ECHO);
2400 if (!appData.icsActive) return;
2401 TelnetRequest(TN_DONT, TN_ECHO);
2405 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2407 /* put the holdings sent to us by the server on the board holdings area */
2408 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2412 if(gameInfo.holdingsWidth < 2) return;
2413 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2414 return; // prevent overwriting by pre-board holdings
2416 if( (int)lowestPiece >= BlackPawn ) {
2419 holdingsStartRow = BOARD_HEIGHT-1;
2422 holdingsColumn = BOARD_WIDTH-1;
2423 countsColumn = BOARD_WIDTH-2;
2424 holdingsStartRow = 0;
2428 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2429 board[i][holdingsColumn] = EmptySquare;
2430 board[i][countsColumn] = (ChessSquare) 0;
2432 while( (p=*holdings++) != NULLCHAR ) {
2433 piece = CharToPiece( ToUpper(p) );
2434 if(piece == EmptySquare) continue;
2435 /*j = (int) piece - (int) WhitePawn;*/
2436 j = PieceToNumber(piece);
2437 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2438 if(j < 0) continue; /* should not happen */
2439 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2440 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2441 board[holdingsStartRow+j*direction][countsColumn]++;
2447 VariantSwitch (Board board, VariantClass newVariant)
2449 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2450 static Board oldBoard;
2452 startedFromPositionFile = FALSE;
2453 if(gameInfo.variant == newVariant) return;
2455 /* [HGM] This routine is called each time an assignment is made to
2456 * gameInfo.variant during a game, to make sure the board sizes
2457 * are set to match the new variant. If that means adding or deleting
2458 * holdings, we shift the playing board accordingly
2459 * This kludge is needed because in ICS observe mode, we get boards
2460 * of an ongoing game without knowing the variant, and learn about the
2461 * latter only later. This can be because of the move list we requested,
2462 * in which case the game history is refilled from the beginning anyway,
2463 * but also when receiving holdings of a crazyhouse game. In the latter
2464 * case we want to add those holdings to the already received position.
2468 if (appData.debugMode) {
2469 fprintf(debugFP, "Switch board from %s to %s\n",
2470 VariantName(gameInfo.variant), VariantName(newVariant));
2471 setbuf(debugFP, NULL);
2473 shuffleOpenings = 0; /* [HGM] shuffle */
2474 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2478 newWidth = 9; newHeight = 9;
2479 gameInfo.holdingsSize = 7;
2480 case VariantBughouse:
2481 case VariantCrazyhouse:
2482 newHoldingsWidth = 2; break;
2486 newHoldingsWidth = 2;
2487 gameInfo.holdingsSize = 8;
2490 case VariantCapablanca:
2491 case VariantCapaRandom:
2494 newHoldingsWidth = gameInfo.holdingsSize = 0;
2497 if(newWidth != gameInfo.boardWidth ||
2498 newHeight != gameInfo.boardHeight ||
2499 newHoldingsWidth != gameInfo.holdingsWidth ) {
2501 /* shift position to new playing area, if needed */
2502 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2503 for(i=0; i<BOARD_HEIGHT; i++)
2504 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2505 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2507 for(i=0; i<newHeight; i++) {
2508 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2509 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2511 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2512 for(i=0; i<BOARD_HEIGHT; i++)
2513 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2514 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2517 board[HOLDINGS_SET] = 0;
2518 gameInfo.boardWidth = newWidth;
2519 gameInfo.boardHeight = newHeight;
2520 gameInfo.holdingsWidth = newHoldingsWidth;
2521 gameInfo.variant = newVariant;
2522 InitDrawingSizes(-2, 0);
2523 } else gameInfo.variant = newVariant;
2524 CopyBoard(oldBoard, board); // remember correctly formatted board
2525 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2526 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2529 static int loggedOn = FALSE;
2531 /*-- Game start info cache: --*/
2533 char gs_kind[MSG_SIZ];
2534 static char player1Name[128] = "";
2535 static char player2Name[128] = "";
2536 static char cont_seq[] = "\n\\ ";
2537 static int player1Rating = -1;
2538 static int player2Rating = -1;
2539 /*----------------------------*/
2541 ColorClass curColor = ColorNormal;
2542 int suppressKibitz = 0;
2545 Boolean soughtPending = FALSE;
2546 Boolean seekGraphUp;
2547 #define MAX_SEEK_ADS 200
2549 char *seekAdList[MAX_SEEK_ADS];
2550 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2551 float tcList[MAX_SEEK_ADS];
2552 char colorList[MAX_SEEK_ADS];
2553 int nrOfSeekAds = 0;
2554 int minRating = 1010, maxRating = 2800;
2555 int hMargin = 10, vMargin = 20, h, w;
2556 extern int squareSize, lineGap;
2561 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2562 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2563 if(r < minRating+100 && r >=0 ) r = minRating+100;
2564 if(r > maxRating) r = maxRating;
2565 if(tc < 1.f) tc = 1.f;
2566 if(tc > 95.f) tc = 95.f;
2567 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2568 y = ((double)r - minRating)/(maxRating - minRating)
2569 * (h-vMargin-squareSize/8-1) + vMargin;
2570 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2571 if(strstr(seekAdList[i], " u ")) color = 1;
2572 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2573 !strstr(seekAdList[i], "bullet") &&
2574 !strstr(seekAdList[i], "blitz") &&
2575 !strstr(seekAdList[i], "standard") ) color = 2;
2576 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2577 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2581 PlotSingleSeekAd (int i)
2587 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2589 char buf[MSG_SIZ], *ext = "";
2590 VariantClass v = StringToVariant(type);
2591 if(strstr(type, "wild")) {
2592 ext = type + 4; // append wild number
2593 if(v == VariantFischeRandom) type = "chess960"; else
2594 if(v == VariantLoadable) type = "setup"; else
2595 type = VariantName(v);
2597 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2598 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2599 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2600 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2601 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2602 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2603 seekNrList[nrOfSeekAds] = nr;
2604 zList[nrOfSeekAds] = 0;
2605 seekAdList[nrOfSeekAds++] = StrSave(buf);
2606 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2611 EraseSeekDot (int i)
2613 int x = xList[i], y = yList[i], d=squareSize/4, k;
2614 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2615 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2616 // now replot every dot that overlapped
2617 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2618 int xx = xList[k], yy = yList[k];
2619 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2620 DrawSeekDot(xx, yy, colorList[k]);
2625 RemoveSeekAd (int nr)
2628 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2630 if(seekAdList[i]) free(seekAdList[i]);
2631 seekAdList[i] = seekAdList[--nrOfSeekAds];
2632 seekNrList[i] = seekNrList[nrOfSeekAds];
2633 ratingList[i] = ratingList[nrOfSeekAds];
2634 colorList[i] = colorList[nrOfSeekAds];
2635 tcList[i] = tcList[nrOfSeekAds];
2636 xList[i] = xList[nrOfSeekAds];
2637 yList[i] = yList[nrOfSeekAds];
2638 zList[i] = zList[nrOfSeekAds];
2639 seekAdList[nrOfSeekAds] = NULL;
2645 MatchSoughtLine (char *line)
2647 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2648 int nr, base, inc, u=0; char dummy;
2650 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2651 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2653 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2654 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2655 // match: compact and save the line
2656 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2666 if(!seekGraphUp) return FALSE;
2667 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2668 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2670 DrawSeekBackground(0, 0, w, h);
2671 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2672 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2673 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2674 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2676 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2679 snprintf(buf, MSG_SIZ, "%d", i);
2680 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2683 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2684 for(i=1; i<100; i+=(i<10?1:5)) {
2685 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2686 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2687 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2689 snprintf(buf, MSG_SIZ, "%d", i);
2690 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2693 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2698 SeekGraphClick (ClickType click, int x, int y, int moving)
2700 static int lastDown = 0, displayed = 0, lastSecond;
2701 if(y < 0) return FALSE;
2702 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2703 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2704 if(!seekGraphUp) return FALSE;
2705 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2706 DrawPosition(TRUE, NULL);
2709 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2710 if(click == Release || moving) return FALSE;
2712 soughtPending = TRUE;
2713 SendToICS(ics_prefix);
2714 SendToICS("sought\n"); // should this be "sought all"?
2715 } else { // issue challenge based on clicked ad
2716 int dist = 10000; int i, closest = 0, second = 0;
2717 for(i=0; i<nrOfSeekAds; i++) {
2718 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2719 if(d < dist) { dist = d; closest = i; }
2720 second += (d - zList[i] < 120); // count in-range ads
2721 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2725 second = (second > 1);
2726 if(displayed != closest || second != lastSecond) {
2727 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2728 lastSecond = second; displayed = closest;
2730 if(click == Press) {
2731 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2734 } // on press 'hit', only show info
2735 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2736 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2737 SendToICS(ics_prefix);
2739 return TRUE; // let incoming board of started game pop down the graph
2740 } else if(click == Release) { // release 'miss' is ignored
2741 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2742 if(moving == 2) { // right up-click
2743 nrOfSeekAds = 0; // refresh graph
2744 soughtPending = TRUE;
2745 SendToICS(ics_prefix);
2746 SendToICS("sought\n"); // should this be "sought all"?
2749 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2750 // press miss or release hit 'pop down' seek graph
2751 seekGraphUp = FALSE;
2752 DrawPosition(TRUE, NULL);
2758 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2760 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2761 #define STARTED_NONE 0
2762 #define STARTED_MOVES 1
2763 #define STARTED_BOARD 2
2764 #define STARTED_OBSERVE 3
2765 #define STARTED_HOLDINGS 4
2766 #define STARTED_CHATTER 5
2767 #define STARTED_COMMENT 6
2768 #define STARTED_MOVES_NOHIDE 7
2770 static int started = STARTED_NONE;
2771 static char parse[20000];
2772 static int parse_pos = 0;
2773 static char buf[BUF_SIZE + 1];
2774 static int firstTime = TRUE, intfSet = FALSE;
2775 static ColorClass prevColor = ColorNormal;
2776 static int savingComment = FALSE;
2777 static int cmatch = 0; // continuation sequence match
2784 int backup; /* [DM] For zippy color lines */
2786 char talker[MSG_SIZ]; // [HGM] chat
2789 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2791 if (appData.debugMode) {
2793 fprintf(debugFP, "<ICS: ");
2794 show_bytes(debugFP, data, count);
2795 fprintf(debugFP, "\n");
2799 if (appData.debugMode) { int f = forwardMostMove;
2800 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2801 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2802 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2805 /* If last read ended with a partial line that we couldn't parse,
2806 prepend it to the new read and try again. */
2807 if (leftover_len > 0) {
2808 for (i=0; i<leftover_len; i++)
2809 buf[i] = buf[leftover_start + i];
2812 /* copy new characters into the buffer */
2813 bp = buf + leftover_len;
2814 buf_len=leftover_len;
2815 for (i=0; i<count; i++)
2818 if (data[i] == '\r')
2821 // join lines split by ICS?
2822 if (!appData.noJoin)
2825 Joining just consists of finding matches against the
2826 continuation sequence, and discarding that sequence
2827 if found instead of copying it. So, until a match
2828 fails, there's nothing to do since it might be the
2829 complete sequence, and thus, something we don't want
2832 if (data[i] == cont_seq[cmatch])
2835 if (cmatch == strlen(cont_seq))
2837 cmatch = 0; // complete match. just reset the counter
2840 it's possible for the ICS to not include the space
2841 at the end of the last word, making our [correct]
2842 join operation fuse two separate words. the server
2843 does this when the space occurs at the width setting.
2845 if (!buf_len || buf[buf_len-1] != ' ')
2856 match failed, so we have to copy what matched before
2857 falling through and copying this character. In reality,
2858 this will only ever be just the newline character, but
2859 it doesn't hurt to be precise.
2861 strncpy(bp, cont_seq, cmatch);
2873 buf[buf_len] = NULLCHAR;
2874 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2879 while (i < buf_len) {
2880 /* Deal with part of the TELNET option negotiation
2881 protocol. We refuse to do anything beyond the
2882 defaults, except that we allow the WILL ECHO option,
2883 which ICS uses to turn off password echoing when we are
2884 directly connected to it. We reject this option
2885 if localLineEditing mode is on (always on in xboard)
2886 and we are talking to port 23, which might be a real
2887 telnet server that will try to keep WILL ECHO on permanently.
2889 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2890 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2891 unsigned char option;
2893 switch ((unsigned char) buf[++i]) {
2895 if (appData.debugMode)
2896 fprintf(debugFP, "\n<WILL ");
2897 switch (option = (unsigned char) buf[++i]) {
2899 if (appData.debugMode)
2900 fprintf(debugFP, "ECHO ");
2901 /* Reply only if this is a change, according
2902 to the protocol rules. */
2903 if (remoteEchoOption) break;
2904 if (appData.localLineEditing &&
2905 atoi(appData.icsPort) == TN_PORT) {
2906 TelnetRequest(TN_DONT, TN_ECHO);
2909 TelnetRequest(TN_DO, TN_ECHO);
2910 remoteEchoOption = TRUE;
2914 if (appData.debugMode)
2915 fprintf(debugFP, "%d ", option);
2916 /* Whatever this is, we don't want it. */
2917 TelnetRequest(TN_DONT, option);
2922 if (appData.debugMode)
2923 fprintf(debugFP, "\n<WONT ");
2924 switch (option = (unsigned char) buf[++i]) {
2926 if (appData.debugMode)
2927 fprintf(debugFP, "ECHO ");
2928 /* Reply only if this is a change, according
2929 to the protocol rules. */
2930 if (!remoteEchoOption) break;
2932 TelnetRequest(TN_DONT, TN_ECHO);
2933 remoteEchoOption = FALSE;
2936 if (appData.debugMode)
2937 fprintf(debugFP, "%d ", (unsigned char) option);
2938 /* Whatever this is, it must already be turned
2939 off, because we never agree to turn on
2940 anything non-default, so according to the
2941 protocol rules, we don't reply. */
2946 if (appData.debugMode)
2947 fprintf(debugFP, "\n<DO ");
2948 switch (option = (unsigned char) buf[++i]) {
2950 /* Whatever this is, we refuse to do it. */
2951 if (appData.debugMode)
2952 fprintf(debugFP, "%d ", option);
2953 TelnetRequest(TN_WONT, option);
2958 if (appData.debugMode)
2959 fprintf(debugFP, "\n<DONT ");
2960 switch (option = (unsigned char) buf[++i]) {
2962 if (appData.debugMode)
2963 fprintf(debugFP, "%d ", option);
2964 /* Whatever this is, we are already not doing
2965 it, because we never agree to do anything
2966 non-default, so according to the protocol
2967 rules, we don't reply. */
2972 if (appData.debugMode)
2973 fprintf(debugFP, "\n<IAC ");
2974 /* Doubled IAC; pass it through */
2978 if (appData.debugMode)
2979 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2980 /* Drop all other telnet commands on the floor */
2983 if (oldi > next_out)
2984 SendToPlayer(&buf[next_out], oldi - next_out);
2990 /* OK, this at least will *usually* work */
2991 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2995 if (loggedOn && !intfSet) {
2996 if (ics_type == ICS_ICC) {
2997 snprintf(str, MSG_SIZ,
2998 "/set-quietly interface %s\n/set-quietly style 12\n",
3000 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3001 strcat(str, "/set-2 51 1\n/set seek 1\n");
3002 } else if (ics_type == ICS_CHESSNET) {
3003 snprintf(str, MSG_SIZ, "/style 12\n");
3005 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3006 strcat(str, programVersion);
3007 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3008 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3009 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3011 strcat(str, "$iset nohighlight 1\n");
3013 strcat(str, "$iset lock 1\n$style 12\n");
3016 NotifyFrontendLogin();
3020 if (started == STARTED_COMMENT) {
3021 /* Accumulate characters in comment */
3022 parse[parse_pos++] = buf[i];
3023 if (buf[i] == '\n') {
3024 parse[parse_pos] = NULLCHAR;
3025 if(chattingPartner>=0) {
3027 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3028 OutputChatMessage(chattingPartner, mess);
3029 chattingPartner = -1;
3030 next_out = i+1; // [HGM] suppress printing in ICS window
3032 if(!suppressKibitz) // [HGM] kibitz
3033 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3034 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3035 int nrDigit = 0, nrAlph = 0, j;
3036 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3037 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3038 parse[parse_pos] = NULLCHAR;
3039 // try to be smart: if it does not look like search info, it should go to
3040 // ICS interaction window after all, not to engine-output window.
3041 for(j=0; j<parse_pos; j++) { // count letters and digits
3042 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3043 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3044 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3046 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3047 int depth=0; float score;
3048 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3049 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3050 pvInfoList[forwardMostMove-1].depth = depth;
3051 pvInfoList[forwardMostMove-1].score = 100*score;
3053 OutputKibitz(suppressKibitz, parse);
3056 if(gameMode == IcsObserving) // restore original ICS messages
3057 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3058 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3060 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3061 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3062 SendToPlayer(tmp, strlen(tmp));
3064 next_out = i+1; // [HGM] suppress printing in ICS window
3066 started = STARTED_NONE;
3068 /* Don't match patterns against characters in comment */
3073 if (started == STARTED_CHATTER) {
3074 if (buf[i] != '\n') {
3075 /* Don't match patterns against characters in chatter */
3079 started = STARTED_NONE;
3080 if(suppressKibitz) next_out = i+1;
3083 /* Kludge to deal with rcmd protocol */
3084 if (firstTime && looking_at(buf, &i, "\001*")) {
3085 DisplayFatalError(&buf[1], 0, 1);
3091 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3094 if (appData.debugMode)
3095 fprintf(debugFP, "ics_type %d\n", ics_type);
3098 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3099 ics_type = ICS_FICS;
3101 if (appData.debugMode)
3102 fprintf(debugFP, "ics_type %d\n", ics_type);
3105 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3106 ics_type = ICS_CHESSNET;
3108 if (appData.debugMode)
3109 fprintf(debugFP, "ics_type %d\n", ics_type);
3114 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3115 looking_at(buf, &i, "Logging you in as \"*\"") ||
3116 looking_at(buf, &i, "will be \"*\""))) {
3117 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3121 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3123 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3124 DisplayIcsInteractionTitle(buf);
3125 have_set_title = TRUE;
3128 /* skip finger notes */
3129 if (started == STARTED_NONE &&
3130 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3131 (buf[i] == '1' && buf[i+1] == '0')) &&
3132 buf[i+2] == ':' && buf[i+3] == ' ') {
3133 started = STARTED_CHATTER;
3139 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3140 if(appData.seekGraph) {
3141 if(soughtPending && MatchSoughtLine(buf+i)) {
3142 i = strstr(buf+i, "rated") - buf;
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144 next_out = leftover_start = i;
3145 started = STARTED_CHATTER;
3146 suppressKibitz = TRUE;
3149 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3150 && looking_at(buf, &i, "* ads displayed")) {
3151 soughtPending = FALSE;
3156 if(appData.autoRefresh) {
3157 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3158 int s = (ics_type == ICS_ICC); // ICC format differs
3160 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3161 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3162 looking_at(buf, &i, "*% "); // eat prompt
3163 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3164 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3165 next_out = i; // suppress
3168 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3169 char *p = star_match[0];
3171 if(seekGraphUp) RemoveSeekAd(atoi(p));
3172 while(*p && *p++ != ' '); // next
3174 looking_at(buf, &i, "*% "); // eat prompt
3175 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3182 /* skip formula vars */
3183 if (started == STARTED_NONE &&
3184 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3185 started = STARTED_CHATTER;
3190 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3191 if (appData.autoKibitz && started == STARTED_NONE &&
3192 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3193 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3194 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3195 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3196 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3197 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3198 suppressKibitz = TRUE;
3199 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3202 && (gameMode == IcsPlayingWhite)) ||
3203 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3204 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3205 started = STARTED_CHATTER; // own kibitz we simply discard
3207 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3208 parse_pos = 0; parse[0] = NULLCHAR;
3209 savingComment = TRUE;
3210 suppressKibitz = gameMode != IcsObserving ? 2 :
3211 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3215 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3216 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3217 && atoi(star_match[0])) {
3218 // suppress the acknowledgements of our own autoKibitz
3220 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3222 SendToPlayer(star_match[0], strlen(star_match[0]));
3223 if(looking_at(buf, &i, "*% ")) // eat prompt
3224 suppressKibitz = FALSE;
3228 } // [HGM] kibitz: end of patch
3230 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3232 // [HGM] chat: intercept tells by users for which we have an open chat window
3234 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3235 looking_at(buf, &i, "* whispers:") ||
3236 looking_at(buf, &i, "* kibitzes:") ||
3237 looking_at(buf, &i, "* shouts:") ||
3238 looking_at(buf, &i, "* c-shouts:") ||
3239 looking_at(buf, &i, "--> * ") ||
3240 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3241 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3242 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3243 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3245 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3246 chattingPartner = -1;
3248 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3249 for(p=0; p<MAX_CHAT; p++) {
3250 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3251 talker[0] = '['; strcat(talker, "] ");
3252 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3253 chattingPartner = p; break;
3256 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3257 for(p=0; p<MAX_CHAT; p++) {
3258 if(!strcmp("kibitzes", chatPartner[p])) {
3259 talker[0] = '['; strcat(talker, "] ");
3260 chattingPartner = p; break;
3263 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3264 for(p=0; p<MAX_CHAT; p++) {
3265 if(!strcmp("whispers", chatPartner[p])) {
3266 talker[0] = '['; strcat(talker, "] ");
3267 chattingPartner = p; break;
3270 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3271 if(buf[i-8] == '-' && buf[i-3] == 't')
3272 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3273 if(!strcmp("c-shouts", chatPartner[p])) {
3274 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3275 chattingPartner = p; break;
3278 if(chattingPartner < 0)
3279 for(p=0; p<MAX_CHAT; p++) {
3280 if(!strcmp("shouts", chatPartner[p])) {
3281 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3282 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3283 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3284 chattingPartner = p; break;
3288 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3289 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3290 talker[0] = 0; Colorize(ColorTell, FALSE);
3291 chattingPartner = p; break;
3293 if(chattingPartner<0) i = oldi; else {
3294 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3295 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3296 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3297 started = STARTED_COMMENT;
3298 parse_pos = 0; parse[0] = NULLCHAR;
3299 savingComment = 3 + chattingPartner; // counts as TRUE
3300 suppressKibitz = TRUE;
3303 } // [HGM] chat: end of patch
3306 if (appData.zippyTalk || appData.zippyPlay) {
3307 /* [DM] Backup address for color zippy lines */
3309 if (loggedOn == TRUE)
3310 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3311 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3313 } // [DM] 'else { ' deleted
3315 /* Regular tells and says */
3316 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3317 looking_at(buf, &i, "* (your partner) tells you: ") ||
3318 looking_at(buf, &i, "* says: ") ||
3319 /* Don't color "message" or "messages" output */
3320 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3321 looking_at(buf, &i, "*. * at *:*: ") ||
3322 looking_at(buf, &i, "--* (*:*): ") ||
3323 /* Message notifications (same color as tells) */
3324 looking_at(buf, &i, "* has left a message ") ||
3325 looking_at(buf, &i, "* just sent you a message:\n") ||
3326 /* Whispers and kibitzes */
3327 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3328 looking_at(buf, &i, "* kibitzes: ") ||
3330 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3332 if (tkind == 1 && strchr(star_match[0], ':')) {
3333 /* Avoid "tells you:" spoofs in channels */
3336 if (star_match[0][0] == NULLCHAR ||
3337 strchr(star_match[0], ' ') ||
3338 (tkind == 3 && strchr(star_match[1], ' '))) {
3339 /* Reject bogus matches */
3342 if (appData.colorize) {
3343 if (oldi > next_out) {
3344 SendToPlayer(&buf[next_out], oldi - next_out);
3349 Colorize(ColorTell, FALSE);
3350 curColor = ColorTell;
3353 Colorize(ColorKibitz, FALSE);
3354 curColor = ColorKibitz;
3357 p = strrchr(star_match[1], '(');
3364 Colorize(ColorChannel1, FALSE);
3365 curColor = ColorChannel1;
3367 Colorize(ColorChannel, FALSE);
3368 curColor = ColorChannel;
3372 curColor = ColorNormal;
3376 if (started == STARTED_NONE && appData.autoComment &&
3377 (gameMode == IcsObserving ||
3378 gameMode == IcsPlayingWhite ||
3379 gameMode == IcsPlayingBlack)) {
3380 parse_pos = i - oldi;
3381 memcpy(parse, &buf[oldi], parse_pos);
3382 parse[parse_pos] = NULLCHAR;
3383 started = STARTED_COMMENT;
3384 savingComment = TRUE;
3386 started = STARTED_CHATTER;
3387 savingComment = FALSE;
3394 if (looking_at(buf, &i, "* s-shouts: ") ||
3395 looking_at(buf, &i, "* c-shouts: ")) {
3396 if (appData.colorize) {
3397 if (oldi > next_out) {
3398 SendToPlayer(&buf[next_out], oldi - next_out);
3401 Colorize(ColorSShout, FALSE);
3402 curColor = ColorSShout;
3405 started = STARTED_CHATTER;
3409 if (looking_at(buf, &i, "--->")) {
3414 if (looking_at(buf, &i, "* shouts: ") ||
3415 looking_at(buf, &i, "--> ")) {
3416 if (appData.colorize) {
3417 if (oldi > next_out) {
3418 SendToPlayer(&buf[next_out], oldi - next_out);
3421 Colorize(ColorShout, FALSE);
3422 curColor = ColorShout;
3425 started = STARTED_CHATTER;
3429 if (looking_at( buf, &i, "Challenge:")) {
3430 if (appData.colorize) {
3431 if (oldi > next_out) {
3432 SendToPlayer(&buf[next_out], oldi - next_out);
3435 Colorize(ColorChallenge, FALSE);
3436 curColor = ColorChallenge;
3442 if (looking_at(buf, &i, "* offers you") ||
3443 looking_at(buf, &i, "* offers to be") ||
3444 looking_at(buf, &i, "* would like to") ||
3445 looking_at(buf, &i, "* requests to") ||
3446 looking_at(buf, &i, "Your opponent offers") ||
3447 looking_at(buf, &i, "Your opponent requests")) {
3449 if (appData.colorize) {
3450 if (oldi > next_out) {
3451 SendToPlayer(&buf[next_out], oldi - next_out);
3454 Colorize(ColorRequest, FALSE);
3455 curColor = ColorRequest;
3460 if (looking_at(buf, &i, "* (*) seeking")) {
3461 if (appData.colorize) {
3462 if (oldi > next_out) {
3463 SendToPlayer(&buf[next_out], oldi - next_out);
3466 Colorize(ColorSeek, FALSE);
3467 curColor = ColorSeek;
3472 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3474 if (looking_at(buf, &i, "\\ ")) {
3475 if (prevColor != ColorNormal) {
3476 if (oldi > next_out) {
3477 SendToPlayer(&buf[next_out], oldi - next_out);
3480 Colorize(prevColor, TRUE);
3481 curColor = prevColor;
3483 if (savingComment) {
3484 parse_pos = i - oldi;
3485 memcpy(parse, &buf[oldi], parse_pos);
3486 parse[parse_pos] = NULLCHAR;
3487 started = STARTED_COMMENT;
3488 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3489 chattingPartner = savingComment - 3; // kludge to remember the box
3491 started = STARTED_CHATTER;
3496 if (looking_at(buf, &i, "Black Strength :") ||
3497 looking_at(buf, &i, "<<< style 10 board >>>") ||
3498 looking_at(buf, &i, "<10>") ||
3499 looking_at(buf, &i, "#@#")) {
3500 /* Wrong board style */
3502 SendToICS(ics_prefix);
3503 SendToICS("set style 12\n");
3504 SendToICS(ics_prefix);
3505 SendToICS("refresh\n");
3509 if (looking_at(buf, &i, "login:")) {
3510 if (!have_sent_ICS_logon) {
3512 have_sent_ICS_logon = 1;
3513 else // no init script was found
3514 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3515 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3516 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3521 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3522 (looking_at(buf, &i, "\n<12> ") ||
3523 looking_at(buf, &i, "<12> "))) {
3525 if (oldi > next_out) {
3526 SendToPlayer(&buf[next_out], oldi - next_out);
3529 started = STARTED_BOARD;
3534 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3535 looking_at(buf, &i, "<b1> ")) {
3536 if (oldi > next_out) {
3537 SendToPlayer(&buf[next_out], oldi - next_out);
3540 started = STARTED_HOLDINGS;
3545 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3547 /* Header for a move list -- first line */
3549 switch (ics_getting_history) {
3553 case BeginningOfGame:
3554 /* User typed "moves" or "oldmoves" while we
3555 were idle. Pretend we asked for these
3556 moves and soak them up so user can step
3557 through them and/or save them.
3560 gameMode = IcsObserving;
3563 ics_getting_history = H_GOT_UNREQ_HEADER;
3565 case EditGame: /*?*/
3566 case EditPosition: /*?*/
3567 /* Should above feature work in these modes too? */
3568 /* For now it doesn't */
3569 ics_getting_history = H_GOT_UNWANTED_HEADER;
3572 ics_getting_history = H_GOT_UNWANTED_HEADER;
3577 /* Is this the right one? */
3578 if (gameInfo.white && gameInfo.black &&
3579 strcmp(gameInfo.white, star_match[0]) == 0 &&
3580 strcmp(gameInfo.black, star_match[2]) == 0) {
3582 ics_getting_history = H_GOT_REQ_HEADER;
3585 case H_GOT_REQ_HEADER:
3586 case H_GOT_UNREQ_HEADER:
3587 case H_GOT_UNWANTED_HEADER:
3588 case H_GETTING_MOVES:
3589 /* Should not happen */
3590 DisplayError(_("Error gathering move list: two headers"), 0);
3591 ics_getting_history = H_FALSE;
3595 /* Save player ratings into gameInfo if needed */
3596 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3597 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3598 (gameInfo.whiteRating == -1 ||
3599 gameInfo.blackRating == -1)) {
3601 gameInfo.whiteRating = string_to_rating(star_match[1]);
3602 gameInfo.blackRating = string_to_rating(star_match[3]);
3603 if (appData.debugMode)
3604 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3605 gameInfo.whiteRating, gameInfo.blackRating);
3610 if (looking_at(buf, &i,
3611 "* * match, initial time: * minute*, increment: * second")) {
3612 /* Header for a move list -- second line */
3613 /* Initial board will follow if this is a wild game */
3614 if (gameInfo.event != NULL) free(gameInfo.event);
3615 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3616 gameInfo.event = StrSave(str);
3617 /* [HGM] we switched variant. Translate boards if needed. */
3618 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3622 if (looking_at(buf, &i, "Move ")) {
3623 /* Beginning of a move list */
3624 switch (ics_getting_history) {
3626 /* Normally should not happen */
3627 /* Maybe user hit reset while we were parsing */
3630 /* Happens if we are ignoring a move list that is not
3631 * the one we just requested. Common if the user
3632 * tries to observe two games without turning off
3635 case H_GETTING_MOVES:
3636 /* Should not happen */
3637 DisplayError(_("Error gathering move list: nested"), 0);
3638 ics_getting_history = H_FALSE;
3640 case H_GOT_REQ_HEADER:
3641 ics_getting_history = H_GETTING_MOVES;
3642 started = STARTED_MOVES;
3644 if (oldi > next_out) {
3645 SendToPlayer(&buf[next_out], oldi - next_out);
3648 case H_GOT_UNREQ_HEADER:
3649 ics_getting_history = H_GETTING_MOVES;
3650 started = STARTED_MOVES_NOHIDE;
3653 case H_GOT_UNWANTED_HEADER:
3654 ics_getting_history = H_FALSE;
3660 if (looking_at(buf, &i, "% ") ||
3661 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3662 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3663 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3664 soughtPending = FALSE;
3668 if(suppressKibitz) next_out = i;
3669 savingComment = FALSE;
3673 case STARTED_MOVES_NOHIDE:
3674 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3675 parse[parse_pos + i - oldi] = NULLCHAR;
3676 ParseGameHistory(parse);
3678 if (appData.zippyPlay && first.initDone) {
3679 FeedMovesToProgram(&first, forwardMostMove);
3680 if (gameMode == IcsPlayingWhite) {
3681 if (WhiteOnMove(forwardMostMove)) {
3682 if (first.sendTime) {
3683 if (first.useColors) {
3684 SendToProgram("black\n", &first);
3686 SendTimeRemaining(&first, TRUE);
3688 if (first.useColors) {
3689 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3691 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3692 first.maybeThinking = TRUE;
3694 if (first.usePlayother) {
3695 if (first.sendTime) {
3696 SendTimeRemaining(&first, TRUE);
3698 SendToProgram("playother\n", &first);
3704 } else if (gameMode == IcsPlayingBlack) {
3705 if (!WhiteOnMove(forwardMostMove)) {
3706 if (first.sendTime) {
3707 if (first.useColors) {
3708 SendToProgram("white\n", &first);
3710 SendTimeRemaining(&first, FALSE);
3712 if (first.useColors) {
3713 SendToProgram("black\n", &first);
3715 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3716 first.maybeThinking = TRUE;
3718 if (first.usePlayother) {
3719 if (first.sendTime) {
3720 SendTimeRemaining(&first, FALSE);
3722 SendToProgram("playother\n", &first);
3731 if (gameMode == IcsObserving && ics_gamenum == -1) {
3732 /* Moves came from oldmoves or moves command
3733 while we weren't doing anything else.
3735 currentMove = forwardMostMove;
3736 ClearHighlights();/*!!could figure this out*/
3737 flipView = appData.flipView;
3738 DrawPosition(TRUE, boards[currentMove]);
3739 DisplayBothClocks();
3740 snprintf(str, MSG_SIZ, "%s %s %s",
3741 gameInfo.white, _("vs."), gameInfo.black);
3745 /* Moves were history of an active game */
3746 if (gameInfo.resultDetails != NULL) {
3747 free(gameInfo.resultDetails);
3748 gameInfo.resultDetails = NULL;
3751 HistorySet(parseList, backwardMostMove,
3752 forwardMostMove, currentMove-1);
3753 DisplayMove(currentMove - 1);
3754 if (started == STARTED_MOVES) next_out = i;
3755 started = STARTED_NONE;
3756 ics_getting_history = H_FALSE;
3759 case STARTED_OBSERVE:
3760 started = STARTED_NONE;
3761 SendToICS(ics_prefix);
3762 SendToICS("refresh\n");
3768 if(bookHit) { // [HGM] book: simulate book reply
3769 static char bookMove[MSG_SIZ]; // a bit generous?
3771 programStats.nodes = programStats.depth = programStats.time =
3772 programStats.score = programStats.got_only_move = 0;
3773 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3775 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3776 strcat(bookMove, bookHit);
3777 HandleMachineMove(bookMove, &first);
3782 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3783 started == STARTED_HOLDINGS ||
3784 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3785 /* Accumulate characters in move list or board */
3786 parse[parse_pos++] = buf[i];
3789 /* Start of game messages. Mostly we detect start of game
3790 when the first board image arrives. On some versions
3791 of the ICS, though, we need to do a "refresh" after starting
3792 to observe in order to get the current board right away. */
3793 if (looking_at(buf, &i, "Adding game * to observation list")) {
3794 started = STARTED_OBSERVE;
3798 /* Handle auto-observe */
3799 if (appData.autoObserve &&
3800 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3801 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3803 /* Choose the player that was highlighted, if any. */
3804 if (star_match[0][0] == '\033' ||
3805 star_match[1][0] != '\033') {
3806 player = star_match[0];
3808 player = star_match[2];
3810 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3811 ics_prefix, StripHighlightAndTitle(player));
3814 /* Save ratings from notify string */
3815 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3816 player1Rating = string_to_rating(star_match[1]);
3817 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3818 player2Rating = string_to_rating(star_match[3]);
3820 if (appData.debugMode)
3822 "Ratings from 'Game notification:' %s %d, %s %d\n",
3823 player1Name, player1Rating,
3824 player2Name, player2Rating);
3829 /* Deal with automatic examine mode after a game,
3830 and with IcsObserving -> IcsExamining transition */
3831 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3832 looking_at(buf, &i, "has made you an examiner of game *")) {
3834 int gamenum = atoi(star_match[0]);
3835 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3836 gamenum == ics_gamenum) {
3837 /* We were already playing or observing this game;
3838 no need to refetch history */
3839 gameMode = IcsExamining;
3841 pauseExamForwardMostMove = forwardMostMove;
3842 } else if (currentMove < forwardMostMove) {
3843 ForwardInner(forwardMostMove);
3846 /* I don't think this case really can happen */
3847 SendToICS(ics_prefix);
3848 SendToICS("refresh\n");
3853 /* Error messages */
3854 // if (ics_user_moved) {
3855 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3856 if (looking_at(buf, &i, "Illegal move") ||
3857 looking_at(buf, &i, "Not a legal move") ||
3858 looking_at(buf, &i, "Your king is in check") ||
3859 looking_at(buf, &i, "It isn't your turn") ||
3860 looking_at(buf, &i, "It is not your move")) {
3862 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3863 currentMove = forwardMostMove-1;
3864 DisplayMove(currentMove - 1); /* before DMError */
3865 DrawPosition(FALSE, boards[currentMove]);
3866 SwitchClocks(forwardMostMove-1); // [HGM] race
3867 DisplayBothClocks();
3869 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg