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((void));
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 lastMsg[MSG_SIZ];
273 ChessSquare pieceSweep = EmptySquare;
274 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
275 int promoDefaultAltered;
276 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278 /* States for ics_getting_history */
280 #define H_REQUESTED 1
281 #define H_GOT_REQ_HEADER 2
282 #define H_GOT_UNREQ_HEADER 3
283 #define H_GETTING_MOVES 4
284 #define H_GOT_UNWANTED_HEADER 5
286 /* whosays values for GameEnds */
295 /* Maximum number of games in a cmail message */
296 #define CMAIL_MAX_GAMES 20
298 /* Different types of move when calling RegisterMove */
300 #define CMAIL_RESIGN 1
302 #define CMAIL_ACCEPT 3
304 /* Different types of result to remember for each game */
305 #define CMAIL_NOT_RESULT 0
306 #define CMAIL_OLD_RESULT 1
307 #define CMAIL_NEW_RESULT 2
309 /* Telnet protocol constants */
320 safeStrCpy (char *dst, const char *src, size_t count)
323 assert( dst != NULL );
324 assert( src != NULL );
327 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
328 if( i == count && dst[count-1] != NULLCHAR)
330 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
331 if(appData.debugMode)
332 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
338 /* Some compiler can't cast u64 to double
339 * This function do the job for us:
341 * We use the highest bit for cast, this only
342 * works if the highest bit is not
343 * in use (This should not happen)
345 * We used this for all compiler
348 u64ToDouble (u64 value)
351 u64 tmp = value & u64Const(0x7fffffffffffffff);
352 r = (double)(s64)tmp;
353 if (value & u64Const(0x8000000000000000))
354 r += 9.2233720368547758080e18; /* 2^63 */
358 /* Fake up flags for now, as we aren't keeping track of castling
359 availability yet. [HGM] Change of logic: the flag now only
360 indicates the type of castlings allowed by the rule of the game.
361 The actual rights themselves are maintained in the array
362 castlingRights, as part of the game history, and are not probed
368 int flags = F_ALL_CASTLE_OK;
369 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
370 switch (gameInfo.variant) {
372 flags &= ~F_ALL_CASTLE_OK;
373 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
374 flags |= F_IGNORE_CHECK;
376 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
379 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381 case VariantKriegspiel:
382 flags |= F_KRIEGSPIEL_CAPTURE;
384 case VariantCapaRandom:
385 case VariantFischeRandom:
386 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
387 case VariantNoCastle:
388 case VariantShatranj:
393 flags &= ~F_ALL_CASTLE_OK;
401 FILE *gameFileFP, *debugFP, *serverFP;
402 char *currentDebugFile; // [HGM] debug split: to remember name
405 [AS] Note: sometimes, the sscanf() function is used to parse the input
406 into a fixed-size buffer. Because of this, we must be prepared to
407 receive strings as long as the size of the input buffer, which is currently
408 set to 4K for Windows and 8K for the rest.
409 So, we must either allocate sufficiently large buffers here, or
410 reduce the size of the input buffer in the input reading part.
413 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
414 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
415 char thinkOutput1[MSG_SIZ*10];
417 ChessProgramState first, second, pairing;
419 /* premove variables */
422 int premoveFromX = 0;
423 int premoveFromY = 0;
424 int premovePromoChar = 0;
426 Boolean alarmSounded;
427 /* end premove variables */
429 char *ics_prefix = "$";
430 enum ICS_TYPE ics_type = ICS_GENERIC;
432 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
433 int pauseExamForwardMostMove = 0;
434 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
435 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
436 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
437 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
438 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
439 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
440 int whiteFlag = FALSE, blackFlag = FALSE;
441 int userOfferedDraw = FALSE;
442 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
443 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
444 int cmailMoveType[CMAIL_MAX_GAMES];
445 long ics_clock_paused = 0;
446 ProcRef icsPR = NoProc, cmailPR = NoProc;
447 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
448 GameMode gameMode = BeginningOfGame;
449 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
450 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
451 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
452 int hiddenThinkOutputState = 0; /* [AS] */
453 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
454 int adjudicateLossPlies = 6;
455 char white_holding[64], black_holding[64];
456 TimeMark lastNodeCountTime;
457 long lastNodeCount=0;
458 int shiftKey, controlKey; // [HGM] set by mouse handler
460 int have_sent_ICS_logon = 0;
462 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
463 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
464 Boolean adjustedClock;
465 long timeControl_2; /* [AS] Allow separate time controls */
466 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
467 long timeRemaining[2][MAX_MOVES];
468 int matchGame = 0, nextGame = 0, roundNr = 0;
469 Boolean waitingForGame = FALSE, startingEngine = FALSE;
470 TimeMark programStartTime, pauseStart;
471 char ics_handle[MSG_SIZ];
472 int have_set_title = 0;
474 /* animateTraining preserves the state of appData.animate
475 * when Training mode is activated. This allows the
476 * response to be animated when appData.animate == TRUE and
477 * appData.animateDragging == TRUE.
479 Boolean animateTraining;
485 Board boards[MAX_MOVES];
486 /* [HGM] Following 7 needed for accurate legality tests: */
487 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
488 signed char initialRights[BOARD_FILES];
489 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
490 int initialRulePlies, FENrulePlies;
491 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 Boolean shuffleOpenings;
494 int mute; // mute all sounds
496 // [HGM] vari: next 12 to save and restore variations
497 #define MAX_VARIATIONS 10
498 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int savedFirst[MAX_VARIATIONS];
501 int savedLast[MAX_VARIATIONS];
502 int savedFramePtr[MAX_VARIATIONS];
503 char *savedDetails[MAX_VARIATIONS];
504 ChessMove savedResult[MAX_VARIATIONS];
506 void PushTail P((int firstMove, int lastMove));
507 Boolean PopTail P((Boolean annotate));
508 void PushInner P((int firstMove, int lastMove));
509 void PopInner P((Boolean annotate));
510 void CleanupTail P((void));
512 ChessSquare FIDEArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
515 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516 BlackKing, BlackBishop, BlackKnight, BlackRook }
519 ChessSquare twoKingsArray[2][BOARD_FILES] = {
520 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
522 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
523 BlackKing, BlackKing, BlackKnight, BlackRook }
526 ChessSquare KnightmateArray[2][BOARD_FILES] = {
527 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
528 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
529 { BlackRook, BlackMan, BlackBishop, BlackQueen,
530 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 ChessSquare SpartanArray[2][BOARD_FILES] = {
534 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
537 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
544 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
549 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
551 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
555 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
556 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackMan, BlackFerz,
558 BlackKing, BlackMan, BlackKnight, BlackRook }
561 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
562 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
563 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
564 { BlackRook, BlackKnight, BlackMan, BlackFerz,
565 BlackKing, BlackMan, BlackKnight, BlackRook }
569 #if (BOARD_FILES>=10)
570 ChessSquare ShogiArray[2][BOARD_FILES] = {
571 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
572 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
573 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
574 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
577 ChessSquare XiangqiArray[2][BOARD_FILES] = {
578 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
579 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
581 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 ChessSquare CapablancaArray[2][BOARD_FILES] = {
585 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
586 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
587 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
588 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
591 ChessSquare GreatArray[2][BOARD_FILES] = {
592 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
593 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
594 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
595 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
598 ChessSquare JanusArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
600 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
601 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
602 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
605 ChessSquare GrandArray[2][BOARD_FILES] = {
606 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
607 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
608 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
609 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
613 ChessSquare GothicArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
615 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
617 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
620 #define GothicArray CapablancaArray
624 ChessSquare FalconArray[2][BOARD_FILES] = {
625 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
626 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
627 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
628 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
631 #define FalconArray CapablancaArray
634 #else // !(BOARD_FILES>=10)
635 #define XiangqiPosition FIDEArray
636 #define CapablancaArray FIDEArray
637 #define GothicArray FIDEArray
638 #define GreatArray FIDEArray
639 #endif // !(BOARD_FILES>=10)
641 #if (BOARD_FILES>=12)
642 ChessSquare CourierArray[2][BOARD_FILES] = {
643 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
644 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
645 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
646 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
648 #else // !(BOARD_FILES>=12)
649 #define CourierArray CapablancaArray
650 #endif // !(BOARD_FILES>=12)
653 Board initialPosition;
656 /* Convert str to a rating. Checks for special cases of "----",
658 "++++", etc. Also strips ()'s */
660 string_to_rating (char *str)
662 while(*str && !isdigit(*str)) ++str;
664 return 0; /* One of the special "no rating" cases */
672 /* Init programStats */
673 programStats.movelist[0] = 0;
674 programStats.depth = 0;
675 programStats.nr_moves = 0;
676 programStats.moves_left = 0;
677 programStats.nodes = 0;
678 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
679 programStats.score = 0;
680 programStats.got_only_move = 0;
681 programStats.got_fail = 0;
682 programStats.line_is_book = 0;
687 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688 if (appData.firstPlaysBlack) {
689 first.twoMachinesColor = "black\n";
690 second.twoMachinesColor = "white\n";
692 first.twoMachinesColor = "white\n";
693 second.twoMachinesColor = "black\n";
696 first.other = &second;
697 second.other = &first;
700 if(appData.timeOddsMode) {
701 norm = appData.timeOdds[0];
702 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
704 first.timeOdds = appData.timeOdds[0]/norm;
705 second.timeOdds = appData.timeOdds[1]/norm;
708 if(programVersion) free(programVersion);
709 if (appData.noChessProgram) {
710 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711 sprintf(programVersion, "%s", PACKAGE_STRING);
713 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
720 UnloadEngine (ChessProgramState *cps)
722 /* Kill off first chess program */
723 if (cps->isr != NULL)
724 RemoveInputSource(cps->isr);
727 if (cps->pr != NoProc) {
729 DoSleep( appData.delayBeforeQuit );
730 SendToProgram("quit\n", cps);
731 DoSleep( appData.delayAfterQuit );
732 DestroyChildProcess(cps->pr, cps->useSigterm);
735 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
739 ClearOptions (ChessProgramState *cps)
742 cps->nrOptions = cps->comboCnt = 0;
743 for(i=0; i<MAX_OPTIONS; i++) {
744 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745 cps->option[i].textValue = 0;
749 char *engineNames[] = {
750 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
751 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
753 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
754 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
759 InitEngine (ChessProgramState *cps, int n)
760 { // [HGM] all engine initialiation put in a function that does one engine
764 cps->which = engineNames[n];
765 cps->maybeThinking = FALSE;
769 cps->sendDrawOffers = 1;
771 cps->program = appData.chessProgram[n];
772 cps->host = appData.host[n];
773 cps->dir = appData.directory[n];
774 cps->initString = appData.engInitString[n];
775 cps->computerString = appData.computerString[n];
776 cps->useSigint = TRUE;
777 cps->useSigterm = TRUE;
778 cps->reuse = appData.reuse[n];
779 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
780 cps->useSetboard = FALSE;
782 cps->usePing = FALSE;
785 cps->usePlayother = FALSE;
786 cps->useColors = TRUE;
787 cps->useUsermove = FALSE;
788 cps->sendICS = FALSE;
789 cps->sendName = appData.icsActive;
790 cps->sdKludge = FALSE;
791 cps->stKludge = FALSE;
792 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
793 TidyProgramName(cps->program, cps->host, cps->tidy);
795 ASSIGN(cps->variants, appData.variant);
796 cps->analysisSupport = 2; /* detect */
797 cps->analyzing = FALSE;
798 cps->initDone = FALSE;
801 /* New features added by Tord: */
802 cps->useFEN960 = FALSE;
803 cps->useOOCastle = TRUE;
804 /* End of new features added by Tord. */
805 cps->fenOverride = appData.fenOverride[n];
807 /* [HGM] time odds: set factor for each machine */
808 cps->timeOdds = appData.timeOdds[n];
810 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
811 cps->accumulateTC = appData.accumulateTC[n];
812 cps->maxNrOfSessions = 1;
817 cps->supportsNPS = UNKNOWN;
818 cps->memSize = FALSE;
819 cps->maxCores = FALSE;
820 ASSIGN(cps->egtFormats, "");
823 cps->optionSettings = appData.engOptions[n];
825 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
826 cps->isUCI = appData.isUCI[n]; /* [AS] */
827 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
829 if (appData.protocolVersion[n] > PROTOVER
830 || appData.protocolVersion[n] < 1)
835 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
836 appData.protocolVersion[n]);
837 if( (len >= MSG_SIZ) && appData.debugMode )
838 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
840 DisplayFatalError(buf, 0, 2);
844 cps->protocolVersion = appData.protocolVersion[n];
847 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
848 ParseFeatures(appData.featureDefaults, cps);
851 ChessProgramState *savCps;
859 if(WaitForEngine(savCps, LoadEngine)) return;
860 CommonEngineInit(); // recalculate time odds
861 if(gameInfo.variant != StringToVariant(appData.variant)) {
862 // we changed variant when loading the engine; this forces us to reset
863 Reset(TRUE, savCps != &first);
864 oldMode = BeginningOfGame; // to prevent restoring old mode
866 InitChessProgram(savCps, FALSE);
867 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
868 DisplayMessage("", "");
869 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
870 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
873 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
877 ReplaceEngine (ChessProgramState *cps, int n)
879 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
881 if(oldMode != BeginningOfGame) EditGameEvent();
884 appData.noChessProgram = FALSE;
885 appData.clockMode = TRUE;
888 if(n) return; // only startup first engine immediately; second can wait
889 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
893 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
894 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
896 static char resetOptions[] =
897 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
898 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
899 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
900 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
903 FloatToFront(char **list, char *engineLine)
905 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
907 if(appData.recentEngines <= 0) return;
908 TidyProgramName(engineLine, "localhost", tidy+1);
909 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
910 strncpy(buf+1, *list, MSG_SIZ-50);
911 if(p = strstr(buf, tidy)) { // tidy name appears in list
912 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
913 while(*p++ = *++q); // squeeze out
915 strcat(tidy, buf+1); // put list behind tidy name
916 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
917 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
918 ASSIGN(*list, tidy+1);
921 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
924 Load (ChessProgramState *cps, int i)
926 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
927 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
928 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
929 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
930 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
931 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
932 appData.firstProtocolVersion = PROTOVER;
933 ParseArgsFromString(buf);
935 ReplaceEngine(cps, i);
936 FloatToFront(&appData.recentEngineList, engineLine);
940 while(q = strchr(p, SLASH)) p = q+1;
941 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
942 if(engineDir[0] != NULLCHAR) {
943 ASSIGN(appData.directory[i], engineDir); p = engineName;
944 } else if(p != engineName) { // derive directory from engine path, when not given
946 ASSIGN(appData.directory[i], engineName);
948 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
949 } else { ASSIGN(appData.directory[i], "."); }
951 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
952 snprintf(command, MSG_SIZ, "%s %s", p, params);
955 ASSIGN(appData.chessProgram[i], p);
956 appData.isUCI[i] = isUCI;
957 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
958 appData.hasOwnBookUCI[i] = hasBook;
959 if(!nickName[0]) useNick = FALSE;
960 if(useNick) ASSIGN(appData.pgnName[i], nickName);
964 q = firstChessProgramNames;
965 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
966 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
967 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
968 quote, p, quote, appData.directory[i],
969 useNick ? " -fn \"" : "",
970 useNick ? nickName : "",
972 v1 ? " -firstProtocolVersion 1" : "",
973 hasBook ? "" : " -fNoOwnBookUCI",
974 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
975 storeVariant ? " -variant " : "",
976 storeVariant ? VariantName(gameInfo.variant) : "");
977 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
978 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
979 if(insert != q) insert[-1] = NULLCHAR;
980 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
982 FloatToFront(&appData.recentEngineList, buf);
984 ReplaceEngine(cps, i);
990 int matched, min, sec;
992 * Parse timeControl resource
994 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
995 appData.movesPerSession)) {
997 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
998 DisplayFatalError(buf, 0, 2);
1002 * Parse searchTime resource
1004 if (*appData.searchTime != NULLCHAR) {
1005 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1007 searchTime = min * 60;
1008 } else if (matched == 2) {
1009 searchTime = min * 60 + sec;
1012 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1013 DisplayFatalError(buf, 0, 2);
1022 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1023 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1025 GetTimeMark(&programStartTime);
1026 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1027 appData.seedBase = random() + (random()<<15);
1028 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1030 ClearProgramStats();
1031 programStats.ok_to_send = 1;
1032 programStats.seen_stat = 0;
1035 * Initialize game list
1041 * Internet chess server status
1043 if (appData.icsActive) {
1044 appData.matchMode = FALSE;
1045 appData.matchGames = 0;
1047 appData.noChessProgram = !appData.zippyPlay;
1049 appData.zippyPlay = FALSE;
1050 appData.zippyTalk = FALSE;
1051 appData.noChessProgram = TRUE;
1053 if (*appData.icsHelper != NULLCHAR) {
1054 appData.useTelnet = TRUE;
1055 appData.telnetProgram = appData.icsHelper;
1058 appData.zippyTalk = appData.zippyPlay = FALSE;
1061 /* [AS] Initialize pv info list [HGM] and game state */
1065 for( i=0; i<=framePtr; i++ ) {
1066 pvInfoList[i].depth = -1;
1067 boards[i][EP_STATUS] = EP_NONE;
1068 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1074 /* [AS] Adjudication threshold */
1075 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1077 InitEngine(&first, 0);
1078 InitEngine(&second, 1);
1081 pairing.which = "pairing"; // pairing engine
1082 pairing.pr = NoProc;
1084 pairing.program = appData.pairingEngine;
1085 pairing.host = "localhost";
1088 if (appData.icsActive) {
1089 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1090 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1091 appData.clockMode = FALSE;
1092 first.sendTime = second.sendTime = 0;
1096 /* Override some settings from environment variables, for backward
1097 compatibility. Unfortunately it's not feasible to have the env
1098 vars just set defaults, at least in xboard. Ugh.
1100 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1105 if (!appData.icsActive) {
1109 /* Check for variants that are supported only in ICS mode,
1110 or not at all. Some that are accepted here nevertheless
1111 have bugs; see comments below.
1113 VariantClass variant = StringToVariant(appData.variant);
1115 case VariantBughouse: /* need four players and two boards */
1116 case VariantKriegspiel: /* need to hide pieces and move details */
1117 /* case VariantFischeRandom: (Fabien: moved below) */
1118 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1119 if( (len >= MSG_SIZ) && appData.debugMode )
1120 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1122 DisplayFatalError(buf, 0, 2);
1125 case VariantUnknown:
1126 case VariantLoadable:
1136 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1137 if( (len >= MSG_SIZ) && appData.debugMode )
1138 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1140 DisplayFatalError(buf, 0, 2);
1143 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1144 case VariantFairy: /* [HGM] TestLegality definitely off! */
1145 case VariantGothic: /* [HGM] should work */
1146 case VariantCapablanca: /* [HGM] should work */
1147 case VariantCourier: /* [HGM] initial forced moves not implemented */
1148 case VariantShogi: /* [HGM] could still mate with pawn drop */
1149 case VariantKnightmate: /* [HGM] should work */
1150 case VariantCylinder: /* [HGM] untested */
1151 case VariantFalcon: /* [HGM] untested */
1152 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1153 offboard interposition not understood */
1154 case VariantNormal: /* definitely works! */
1155 case VariantWildCastle: /* pieces not automatically shuffled */
1156 case VariantNoCastle: /* pieces not automatically shuffled */
1157 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1158 case VariantLosers: /* should work except for win condition,
1159 and doesn't know captures are mandatory */
1160 case VariantSuicide: /* should work except for win condition,
1161 and doesn't know captures are mandatory */
1162 case VariantGiveaway: /* should work except for win condition,
1163 and doesn't know captures are mandatory */
1164 case VariantTwoKings: /* should work */
1165 case VariantAtomic: /* should work except for win condition */
1166 case Variant3Check: /* should work except for win condition */
1167 case VariantShatranj: /* should work except for all win conditions */
1168 case VariantMakruk: /* should work except for draw countdown */
1169 case VariantASEAN : /* should work except for draw countdown */
1170 case VariantBerolina: /* might work if TestLegality is off */
1171 case VariantCapaRandom: /* should work */
1172 case VariantJanus: /* should work */
1173 case VariantSuper: /* experimental */
1174 case VariantGreat: /* experimental, requires legality testing to be off */
1175 case VariantSChess: /* S-Chess, should work */
1176 case VariantGrand: /* should work */
1177 case VariantSpartan: /* should work */
1185 NextIntegerFromString (char ** str, long * value)
1190 while( *s == ' ' || *s == '\t' ) {
1196 if( *s >= '0' && *s <= '9' ) {
1197 while( *s >= '0' && *s <= '9' ) {
1198 *value = *value * 10 + (*s - '0');
1211 NextTimeControlFromString (char ** str, long * value)
1214 int result = NextIntegerFromString( str, &temp );
1217 *value = temp * 60; /* Minutes */
1218 if( **str == ':' ) {
1220 result = NextIntegerFromString( str, &temp );
1221 *value += temp; /* Seconds */
1229 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1230 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1231 int result = -1, type = 0; long temp, temp2;
1233 if(**str != ':') return -1; // old params remain in force!
1235 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1236 if( NextIntegerFromString( str, &temp ) ) return -1;
1237 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1240 /* time only: incremental or sudden-death time control */
1241 if(**str == '+') { /* increment follows; read it */
1243 if(**str == '!') type = *(*str)++; // Bronstein TC
1244 if(result = NextIntegerFromString( str, &temp2)) return -1;
1245 *inc = temp2 * 1000;
1246 if(**str == '.') { // read fraction of increment
1247 char *start = ++(*str);
1248 if(result = NextIntegerFromString( str, &temp2)) return -1;
1250 while(start++ < *str) temp2 /= 10;
1254 *moves = 0; *tc = temp * 1000; *incType = type;
1258 (*str)++; /* classical time control */
1259 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1271 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1272 { /* [HGM] get time to add from the multi-session time-control string */
1273 int incType, moves=1; /* kludge to force reading of first session */
1274 long time, increment;
1277 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1279 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1280 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1281 if(movenr == -1) return time; /* last move before new session */
1282 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1283 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1284 if(!moves) return increment; /* current session is incremental */
1285 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1286 } while(movenr >= -1); /* try again for next session */
1288 return 0; // no new time quota on this move
1292 ParseTimeControl (char *tc, float ti, int mps)
1296 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1299 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1300 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1301 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1305 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1307 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1310 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1312 snprintf(buf, MSG_SIZ, ":%s", mytc);
1314 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1316 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1321 /* Parse second time control */
1324 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1332 timeControl_2 = tc2 * 1000;
1342 timeControl = tc1 * 1000;
1345 timeIncrement = ti * 1000; /* convert to ms */
1346 movesPerSession = 0;
1349 movesPerSession = mps;
1357 if (appData.debugMode) {
1358 # ifdef __GIT_VERSION
1359 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1361 fprintf(debugFP, "Version: %s\n", programVersion);
1364 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1366 set_cont_sequence(appData.wrapContSeq);
1367 if (appData.matchGames > 0) {
1368 appData.matchMode = TRUE;
1369 } else if (appData.matchMode) {
1370 appData.matchGames = 1;
1372 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1373 appData.matchGames = appData.sameColorGames;
1374 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1375 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1376 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1379 if (appData.noChessProgram || first.protocolVersion == 1) {
1382 /* kludge: allow timeout for initial "feature" commands */
1384 DisplayMessage("", _("Starting chess program"));
1385 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1390 CalculateIndex (int index, int gameNr)
1391 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1393 if(index > 0) return index; // fixed nmber
1394 if(index == 0) return 1;
1395 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1396 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1401 LoadGameOrPosition (int gameNr)
1402 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1403 if (*appData.loadGameFile != NULLCHAR) {
1404 if (!LoadGameFromFile(appData.loadGameFile,
1405 CalculateIndex(appData.loadGameIndex, gameNr),
1406 appData.loadGameFile, FALSE)) {
1407 DisplayFatalError(_("Bad game file"), 0, 1);
1410 } else if (*appData.loadPositionFile != NULLCHAR) {
1411 if (!LoadPositionFromFile(appData.loadPositionFile,
1412 CalculateIndex(appData.loadPositionIndex, gameNr),
1413 appData.loadPositionFile)) {
1414 DisplayFatalError(_("Bad position file"), 0, 1);
1422 ReserveGame (int gameNr, char resChar)
1424 FILE *tf = fopen(appData.tourneyFile, "r+");
1425 char *p, *q, c, buf[MSG_SIZ];
1426 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1427 safeStrCpy(buf, lastMsg, MSG_SIZ);
1428 DisplayMessage(_("Pick new game"), "");
1429 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1430 ParseArgsFromFile(tf);
1431 p = q = appData.results;
1432 if(appData.debugMode) {
1433 char *r = appData.participants;
1434 fprintf(debugFP, "results = '%s'\n", p);
1435 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1436 fprintf(debugFP, "\n");
1438 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1440 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1441 safeStrCpy(q, p, strlen(p) + 2);
1442 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1443 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1444 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1445 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1448 fseek(tf, -(strlen(p)+4), SEEK_END);
1450 if(c != '"') // depending on DOS or Unix line endings we can be one off
1451 fseek(tf, -(strlen(p)+2), SEEK_END);
1452 else fseek(tf, -(strlen(p)+3), SEEK_END);
1453 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1454 DisplayMessage(buf, "");
1455 free(p); appData.results = q;
1456 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1457 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1458 int round = appData.defaultMatchGames * appData.tourneyType;
1459 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1460 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1461 UnloadEngine(&first); // next game belongs to other pairing;
1462 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1464 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1468 MatchEvent (int mode)
1469 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1471 if(matchMode) { // already in match mode: switch it off
1473 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1476 // if(gameMode != BeginningOfGame) {
1477 // DisplayError(_("You can only start a match from the initial position."), 0);
1481 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1482 /* Set up machine vs. machine match */
1484 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1485 if(appData.tourneyFile[0]) {
1487 if(nextGame > appData.matchGames) {
1489 if(strchr(appData.results, '*') == NULL) {
1491 appData.tourneyCycles++;
1492 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1494 NextTourneyGame(-1, &dummy);
1496 if(nextGame <= appData.matchGames) {
1497 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1499 ScheduleDelayedEvent(NextMatchGame, 10000);
1504 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1505 DisplayError(buf, 0);
1506 appData.tourneyFile[0] = 0;
1510 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1511 DisplayFatalError(_("Can't have a match with no chess programs"),
1516 matchGame = roundNr = 1;
1517 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1521 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1524 InitBackEnd3 P((void))
1526 GameMode initialMode;
1530 InitChessProgram(&first, startedFromSetupPosition);
1532 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1533 free(programVersion);
1534 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1535 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1536 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1539 if (appData.icsActive) {
1541 /* [DM] Make a console window if needed [HGM] merged ifs */
1547 if (*appData.icsCommPort != NULLCHAR)
1548 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1549 appData.icsCommPort);
1551 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1552 appData.icsHost, appData.icsPort);
1554 if( (len >= MSG_SIZ) && appData.debugMode )
1555 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1557 DisplayFatalError(buf, err, 1);
1562 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1564 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1565 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1566 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1567 } else if (appData.noChessProgram) {
1573 if (*appData.cmailGameName != NULLCHAR) {
1575 OpenLoopback(&cmailPR);
1577 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1581 DisplayMessage("", "");
1582 if (StrCaseCmp(appData.initialMode, "") == 0) {
1583 initialMode = BeginningOfGame;
1584 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1585 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1586 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1587 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1590 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1591 initialMode = TwoMachinesPlay;
1592 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1593 initialMode = AnalyzeFile;
1594 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1595 initialMode = AnalyzeMode;
1596 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1597 initialMode = MachinePlaysWhite;
1598 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1599 initialMode = MachinePlaysBlack;
1600 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1601 initialMode = EditGame;
1602 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1603 initialMode = EditPosition;
1604 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1605 initialMode = Training;
1607 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1608 if( (len >= MSG_SIZ) && appData.debugMode )
1609 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1611 DisplayFatalError(buf, 0, 2);
1615 if (appData.matchMode) {
1616 if(appData.tourneyFile[0]) { // start tourney from command line
1618 if(f = fopen(appData.tourneyFile, "r")) {
1619 ParseArgsFromFile(f); // make sure tourney parmeters re known
1621 appData.clockMode = TRUE;
1623 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1626 } else if (*appData.cmailGameName != NULLCHAR) {
1627 /* Set up cmail mode */
1628 ReloadCmailMsgEvent(TRUE);
1630 /* Set up other modes */
1631 if (initialMode == AnalyzeFile) {
1632 if (*appData.loadGameFile == NULLCHAR) {
1633 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1637 if (*appData.loadGameFile != NULLCHAR) {
1638 (void) LoadGameFromFile(appData.loadGameFile,
1639 appData.loadGameIndex,
1640 appData.loadGameFile, TRUE);
1641 } else if (*appData.loadPositionFile != NULLCHAR) {
1642 (void) LoadPositionFromFile(appData.loadPositionFile,
1643 appData.loadPositionIndex,
1644 appData.loadPositionFile);
1645 /* [HGM] try to make self-starting even after FEN load */
1646 /* to allow automatic setup of fairy variants with wtm */
1647 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1648 gameMode = BeginningOfGame;
1649 setboardSpoiledMachineBlack = 1;
1651 /* [HGM] loadPos: make that every new game uses the setup */
1652 /* from file as long as we do not switch variant */
1653 if(!blackPlaysFirst) {
1654 startedFromPositionFile = TRUE;
1655 CopyBoard(filePosition, boards[0]);
1658 if (initialMode == AnalyzeMode) {
1659 if (appData.noChessProgram) {
1660 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1663 if (appData.icsActive) {
1664 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1668 } else if (initialMode == AnalyzeFile) {
1669 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1670 ShowThinkingEvent();
1672 AnalysisPeriodicEvent(1);
1673 } else if (initialMode == MachinePlaysWhite) {
1674 if (appData.noChessProgram) {
1675 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1679 if (appData.icsActive) {
1680 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1684 MachineWhiteEvent();
1685 } else if (initialMode == MachinePlaysBlack) {
1686 if (appData.noChessProgram) {
1687 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1691 if (appData.icsActive) {
1692 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1696 MachineBlackEvent();
1697 } else if (initialMode == TwoMachinesPlay) {
1698 if (appData.noChessProgram) {
1699 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1703 if (appData.icsActive) {
1704 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1709 } else if (initialMode == EditGame) {
1711 } else if (initialMode == EditPosition) {
1712 EditPositionEvent();
1713 } else if (initialMode == Training) {
1714 if (*appData.loadGameFile == NULLCHAR) {
1715 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1724 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1726 DisplayBook(current+1);
1728 MoveHistorySet( movelist, first, last, current, pvInfoList );
1730 EvalGraphSet( first, last, current, pvInfoList );
1732 MakeEngineOutputTitle();
1736 * Establish will establish a contact to a remote host.port.
1737 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1738 * used to talk to the host.
1739 * Returns 0 if okay, error code if not.
1746 if (*appData.icsCommPort != NULLCHAR) {
1747 /* Talk to the host through a serial comm port */
1748 return OpenCommPort(appData.icsCommPort, &icsPR);
1750 } else if (*appData.gateway != NULLCHAR) {
1751 if (*appData.remoteShell == NULLCHAR) {
1752 /* Use the rcmd protocol to run telnet program on a gateway host */
1753 snprintf(buf, sizeof(buf), "%s %s %s",
1754 appData.telnetProgram, appData.icsHost, appData.icsPort);
1755 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1758 /* Use the rsh program to run telnet program on a gateway host */
1759 if (*appData.remoteUser == NULLCHAR) {
1760 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1761 appData.gateway, appData.telnetProgram,
1762 appData.icsHost, appData.icsPort);
1764 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1765 appData.remoteShell, appData.gateway,
1766 appData.remoteUser, appData.telnetProgram,
1767 appData.icsHost, appData.icsPort);
1769 return StartChildProcess(buf, "", &icsPR);
1772 } else if (appData.useTelnet) {
1773 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1776 /* TCP socket interface differs somewhat between
1777 Unix and NT; handle details in the front end.
1779 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1784 EscapeExpand (char *p, char *q)
1785 { // [HGM] initstring: routine to shape up string arguments
1786 while(*p++ = *q++) if(p[-1] == '\\')
1788 case 'n': p[-1] = '\n'; break;
1789 case 'r': p[-1] = '\r'; break;
1790 case 't': p[-1] = '\t'; break;
1791 case '\\': p[-1] = '\\'; break;
1792 case 0: *p = 0; return;
1793 default: p[-1] = q[-1]; break;
1798 show_bytes (FILE *fp, char *buf, int count)
1801 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1802 fprintf(fp, "\\%03o", *buf & 0xff);
1811 /* Returns an errno value */
1813 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1815 char buf[8192], *p, *q, *buflim;
1816 int left, newcount, outcount;
1818 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1819 *appData.gateway != NULLCHAR) {
1820 if (appData.debugMode) {
1821 fprintf(debugFP, ">ICS: ");
1822 show_bytes(debugFP, message, count);
1823 fprintf(debugFP, "\n");
1825 return OutputToProcess(pr, message, count, outError);
1828 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1835 if (appData.debugMode) {
1836 fprintf(debugFP, ">ICS: ");
1837 show_bytes(debugFP, buf, newcount);
1838 fprintf(debugFP, "\n");
1840 outcount = OutputToProcess(pr, buf, newcount, outError);
1841 if (outcount < newcount) return -1; /* to be sure */
1848 } else if (((unsigned char) *p) == TN_IAC) {
1849 *q++ = (char) TN_IAC;
1856 if (appData.debugMode) {
1857 fprintf(debugFP, ">ICS: ");
1858 show_bytes(debugFP, buf, newcount);
1859 fprintf(debugFP, "\n");
1861 outcount = OutputToProcess(pr, buf, newcount, outError);
1862 if (outcount < newcount) return -1; /* to be sure */
1867 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1869 int outError, outCount;
1870 static int gotEof = 0;
1873 /* Pass data read from player on to ICS */
1876 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1877 if (outCount < count) {
1878 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880 if(have_sent_ICS_logon == 2) {
1881 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1882 fprintf(ini, "%s", message);
1883 have_sent_ICS_logon = 3;
1885 have_sent_ICS_logon = 1;
1886 } else if(have_sent_ICS_logon == 3) {
1887 fprintf(ini, "%s", message);
1889 have_sent_ICS_logon = 1;
1891 } else if (count < 0) {
1892 RemoveInputSource(isr);
1893 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1894 } else if (gotEof++ > 0) {
1895 RemoveInputSource(isr);
1896 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1902 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1903 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1904 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1905 SendToICS("date\n");
1906 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1909 /* added routine for printf style output to ics */
1911 ics_printf (char *format, ...)
1913 char buffer[MSG_SIZ];
1916 va_start(args, format);
1917 vsnprintf(buffer, sizeof(buffer), format, args);
1918 buffer[sizeof(buffer)-1] = '\0';
1926 int count, outCount, outError;
1928 if (icsPR == NoProc) return;
1931 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1932 if (outCount < count) {
1933 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1937 /* This is used for sending logon scripts to the ICS. Sending
1938 without a delay causes problems when using timestamp on ICC
1939 (at least on my machine). */
1941 SendToICSDelayed (char *s, long msdelay)
1943 int count, outCount, outError;
1945 if (icsPR == NoProc) return;
1948 if (appData.debugMode) {
1949 fprintf(debugFP, ">ICS: ");
1950 show_bytes(debugFP, s, count);
1951 fprintf(debugFP, "\n");
1953 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1955 if (outCount < count) {
1956 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1961 /* Remove all highlighting escape sequences in s
1962 Also deletes any suffix starting with '('
1965 StripHighlightAndTitle (char *s)
1967 static char retbuf[MSG_SIZ];
1970 while (*s != NULLCHAR) {
1971 while (*s == '\033') {
1972 while (*s != NULLCHAR && !isalpha(*s)) s++;
1973 if (*s != NULLCHAR) s++;
1975 while (*s != NULLCHAR && *s != '\033') {
1976 if (*s == '(' || *s == '[') {
1987 /* Remove all highlighting escape sequences in s */
1989 StripHighlight (char *s)
1991 static char retbuf[MSG_SIZ];
1994 while (*s != NULLCHAR) {
1995 while (*s == '\033') {
1996 while (*s != NULLCHAR && !isalpha(*s)) s++;
1997 if (*s != NULLCHAR) s++;
1999 while (*s != NULLCHAR && *s != '\033') {
2007 char *variantNames[] = VARIANT_NAMES;
2009 VariantName (VariantClass v)
2011 return variantNames[v];
2015 /* Identify a variant from the strings the chess servers use or the
2016 PGN Variant tag names we use. */
2018 StringToVariant (char *e)
2022 VariantClass v = VariantNormal;
2023 int i, found = FALSE;
2029 /* [HGM] skip over optional board-size prefixes */
2030 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2031 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2032 while( *e++ != '_');
2035 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2039 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2040 if (StrCaseStr(e, variantNames[i])) {
2041 v = (VariantClass) i;
2048 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2049 || StrCaseStr(e, "wild/fr")
2050 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2051 v = VariantFischeRandom;
2052 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2053 (i = 1, p = StrCaseStr(e, "w"))) {
2055 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2062 case 0: /* FICS only, actually */
2064 /* Castling legal even if K starts on d-file */
2065 v = VariantWildCastle;
2070 /* Castling illegal even if K & R happen to start in
2071 normal positions. */
2072 v = VariantNoCastle;
2085 /* Castling legal iff K & R start in normal positions */
2091 /* Special wilds for position setup; unclear what to do here */
2092 v = VariantLoadable;
2095 /* Bizarre ICC game */
2096 v = VariantTwoKings;
2099 v = VariantKriegspiel;
2105 v = VariantFischeRandom;
2108 v = VariantCrazyhouse;
2111 v = VariantBughouse;
2117 /* Not quite the same as FICS suicide! */
2118 v = VariantGiveaway;
2124 v = VariantShatranj;
2127 /* Temporary names for future ICC types. The name *will* change in
2128 the next xboard/WinBoard release after ICC defines it. */
2166 v = VariantCapablanca;
2169 v = VariantKnightmate;
2175 v = VariantCylinder;
2181 v = VariantCapaRandom;
2184 v = VariantBerolina;
2196 /* Found "wild" or "w" in the string but no number;
2197 must assume it's normal chess. */
2201 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2202 if( (len >= MSG_SIZ) && appData.debugMode )
2203 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2205 DisplayError(buf, 0);
2211 if (appData.debugMode) {
2212 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2213 e, wnum, VariantName(v));
2218 static int leftover_start = 0, leftover_len = 0;
2219 char star_match[STAR_MATCH_N][MSG_SIZ];
2221 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2222 advance *index beyond it, and set leftover_start to the new value of
2223 *index; else return FALSE. If pattern contains the character '*', it
2224 matches any sequence of characters not containing '\r', '\n', or the
2225 character following the '*' (if any), and the matched sequence(s) are
2226 copied into star_match.
2229 looking_at ( char *buf, int *index, char *pattern)
2231 char *bufp = &buf[*index], *patternp = pattern;
2233 char *matchp = star_match[0];
2236 if (*patternp == NULLCHAR) {
2237 *index = leftover_start = bufp - buf;
2241 if (*bufp == NULLCHAR) return FALSE;
2242 if (*patternp == '*') {
2243 if (*bufp == *(patternp + 1)) {
2245 matchp = star_match[++star_count];
2249 } else if (*bufp == '\n' || *bufp == '\r') {
2251 if (*patternp == NULLCHAR)
2256 *matchp++ = *bufp++;
2260 if (*patternp != *bufp) return FALSE;
2267 SendToPlayer (char *data, int length)
2269 int error, outCount;
2270 outCount = OutputToProcess(NoProc, data, length, &error);
2271 if (outCount < length) {
2272 DisplayFatalError(_("Error writing to display"), error, 1);
2277 PackHolding (char packed[], char *holding)
2287 switch (runlength) {
2298 sprintf(q, "%d", runlength);
2310 /* Telnet protocol requests from the front end */
2312 TelnetRequest (unsigned char ddww, unsigned char option)
2314 unsigned char msg[3];
2315 int outCount, outError;
2317 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2319 if (appData.debugMode) {
2320 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2336 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2345 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2348 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2353 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2355 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2362 if (!appData.icsActive) return;
2363 TelnetRequest(TN_DO, TN_ECHO);
2369 if (!appData.icsActive) return;
2370 TelnetRequest(TN_DONT, TN_ECHO);
2374 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2376 /* put the holdings sent to us by the server on the board holdings area */
2377 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2381 if(gameInfo.holdingsWidth < 2) return;
2382 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2383 return; // prevent overwriting by pre-board holdings
2385 if( (int)lowestPiece >= BlackPawn ) {
2388 holdingsStartRow = BOARD_HEIGHT-1;
2391 holdingsColumn = BOARD_WIDTH-1;
2392 countsColumn = BOARD_WIDTH-2;
2393 holdingsStartRow = 0;
2397 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2398 board[i][holdingsColumn] = EmptySquare;
2399 board[i][countsColumn] = (ChessSquare) 0;
2401 while( (p=*holdings++) != NULLCHAR ) {
2402 piece = CharToPiece( ToUpper(p) );
2403 if(piece == EmptySquare) continue;
2404 /*j = (int) piece - (int) WhitePawn;*/
2405 j = PieceToNumber(piece);
2406 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2407 if(j < 0) continue; /* should not happen */
2408 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2409 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2410 board[holdingsStartRow+j*direction][countsColumn]++;
2416 VariantSwitch (Board board, VariantClass newVariant)
2418 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2419 static Board oldBoard;
2421 startedFromPositionFile = FALSE;
2422 if(gameInfo.variant == newVariant) return;
2424 /* [HGM] This routine is called each time an assignment is made to
2425 * gameInfo.variant during a game, to make sure the board sizes
2426 * are set to match the new variant. If that means adding or deleting
2427 * holdings, we shift the playing board accordingly
2428 * This kludge is needed because in ICS observe mode, we get boards
2429 * of an ongoing game without knowing the variant, and learn about the
2430 * latter only later. This can be because of the move list we requested,
2431 * in which case the game history is refilled from the beginning anyway,
2432 * but also when receiving holdings of a crazyhouse game. In the latter
2433 * case we want to add those holdings to the already received position.
2437 if (appData.debugMode) {
2438 fprintf(debugFP, "Switch board from %s to %s\n",
2439 VariantName(gameInfo.variant), VariantName(newVariant));
2440 setbuf(debugFP, NULL);
2442 shuffleOpenings = 0; /* [HGM] shuffle */
2443 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2447 newWidth = 9; newHeight = 9;
2448 gameInfo.holdingsSize = 7;
2449 case VariantBughouse:
2450 case VariantCrazyhouse:
2451 newHoldingsWidth = 2; break;
2455 newHoldingsWidth = 2;
2456 gameInfo.holdingsSize = 8;
2459 case VariantCapablanca:
2460 case VariantCapaRandom:
2463 newHoldingsWidth = gameInfo.holdingsSize = 0;
2466 if(newWidth != gameInfo.boardWidth ||
2467 newHeight != gameInfo.boardHeight ||
2468 newHoldingsWidth != gameInfo.holdingsWidth ) {
2470 /* shift position to new playing area, if needed */
2471 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2472 for(i=0; i<BOARD_HEIGHT; i++)
2473 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2474 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2476 for(i=0; i<newHeight; i++) {
2477 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2478 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2480 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2481 for(i=0; i<BOARD_HEIGHT; i++)
2482 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2483 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2486 board[HOLDINGS_SET] = 0;
2487 gameInfo.boardWidth = newWidth;
2488 gameInfo.boardHeight = newHeight;
2489 gameInfo.holdingsWidth = newHoldingsWidth;
2490 gameInfo.variant = newVariant;
2491 InitDrawingSizes(-2, 0);
2492 } else gameInfo.variant = newVariant;
2493 CopyBoard(oldBoard, board); // remember correctly formatted board
2494 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2495 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2498 static int loggedOn = FALSE;
2500 /*-- Game start info cache: --*/
2502 char gs_kind[MSG_SIZ];
2503 static char player1Name[128] = "";
2504 static char player2Name[128] = "";
2505 static char cont_seq[] = "\n\\ ";
2506 static int player1Rating = -1;
2507 static int player2Rating = -1;
2508 /*----------------------------*/
2510 ColorClass curColor = ColorNormal;
2511 int suppressKibitz = 0;
2514 Boolean soughtPending = FALSE;
2515 Boolean seekGraphUp;
2516 #define MAX_SEEK_ADS 200
2518 char *seekAdList[MAX_SEEK_ADS];
2519 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2520 float tcList[MAX_SEEK_ADS];
2521 char colorList[MAX_SEEK_ADS];
2522 int nrOfSeekAds = 0;
2523 int minRating = 1010, maxRating = 2800;
2524 int hMargin = 10, vMargin = 20, h, w;
2525 extern int squareSize, lineGap;
2530 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2531 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2532 if(r < minRating+100 && r >=0 ) r = minRating+100;
2533 if(r > maxRating) r = maxRating;
2534 if(tc < 1.f) tc = 1.f;
2535 if(tc > 95.f) tc = 95.f;
2536 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2537 y = ((double)r - minRating)/(maxRating - minRating)
2538 * (h-vMargin-squareSize/8-1) + vMargin;
2539 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2540 if(strstr(seekAdList[i], " u ")) color = 1;
2541 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2542 !strstr(seekAdList[i], "bullet") &&
2543 !strstr(seekAdList[i], "blitz") &&
2544 !strstr(seekAdList[i], "standard") ) color = 2;
2545 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2546 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2550 PlotSingleSeekAd (int i)
2556 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2558 char buf[MSG_SIZ], *ext = "";
2559 VariantClass v = StringToVariant(type);
2560 if(strstr(type, "wild")) {
2561 ext = type + 4; // append wild number
2562 if(v == VariantFischeRandom) type = "chess960"; else
2563 if(v == VariantLoadable) type = "setup"; else
2564 type = VariantName(v);
2566 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2567 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2568 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2569 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2570 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2571 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2572 seekNrList[nrOfSeekAds] = nr;
2573 zList[nrOfSeekAds] = 0;
2574 seekAdList[nrOfSeekAds++] = StrSave(buf);
2575 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2580 EraseSeekDot (int i)
2582 int x = xList[i], y = yList[i], d=squareSize/4, k;
2583 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2584 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2585 // now replot every dot that overlapped
2586 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2587 int xx = xList[k], yy = yList[k];
2588 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2589 DrawSeekDot(xx, yy, colorList[k]);
2594 RemoveSeekAd (int nr)
2597 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2599 if(seekAdList[i]) free(seekAdList[i]);
2600 seekAdList[i] = seekAdList[--nrOfSeekAds];
2601 seekNrList[i] = seekNrList[nrOfSeekAds];
2602 ratingList[i] = ratingList[nrOfSeekAds];
2603 colorList[i] = colorList[nrOfSeekAds];
2604 tcList[i] = tcList[nrOfSeekAds];
2605 xList[i] = xList[nrOfSeekAds];
2606 yList[i] = yList[nrOfSeekAds];
2607 zList[i] = zList[nrOfSeekAds];
2608 seekAdList[nrOfSeekAds] = NULL;
2614 MatchSoughtLine (char *line)
2616 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2617 int nr, base, inc, u=0; char dummy;
2619 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2620 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2622 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2623 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2624 // match: compact and save the line
2625 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2635 if(!seekGraphUp) return FALSE;
2636 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2637 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2639 DrawSeekBackground(0, 0, w, h);
2640 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2641 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2642 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2643 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2645 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2648 snprintf(buf, MSG_SIZ, "%d", i);
2649 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2652 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2653 for(i=1; i<100; i+=(i<10?1:5)) {
2654 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2655 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2656 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2658 snprintf(buf, MSG_SIZ, "%d", i);
2659 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2662 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2667 SeekGraphClick (ClickType click, int x, int y, int moving)
2669 static int lastDown = 0, displayed = 0, lastSecond;
2670 if(y < 0) return FALSE;
2671 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2672 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2673 if(!seekGraphUp) return FALSE;
2674 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2675 DrawPosition(TRUE, NULL);
2678 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2679 if(click == Release || moving) return FALSE;
2681 soughtPending = TRUE;
2682 SendToICS(ics_prefix);
2683 SendToICS("sought\n"); // should this be "sought all"?
2684 } else { // issue challenge based on clicked ad
2685 int dist = 10000; int i, closest = 0, second = 0;
2686 for(i=0; i<nrOfSeekAds; i++) {
2687 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2688 if(d < dist) { dist = d; closest = i; }
2689 second += (d - zList[i] < 120); // count in-range ads
2690 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2694 second = (second > 1);
2695 if(displayed != closest || second != lastSecond) {
2696 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2697 lastSecond = second; displayed = closest;
2699 if(click == Press) {
2700 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2703 } // on press 'hit', only show info
2704 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2705 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2706 SendToICS(ics_prefix);
2708 return TRUE; // let incoming board of started game pop down the graph
2709 } else if(click == Release) { // release 'miss' is ignored
2710 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2711 if(moving == 2) { // right up-click
2712 nrOfSeekAds = 0; // refresh graph
2713 soughtPending = TRUE;
2714 SendToICS(ics_prefix);
2715 SendToICS("sought\n"); // should this be "sought all"?
2718 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2719 // press miss or release hit 'pop down' seek graph
2720 seekGraphUp = FALSE;
2721 DrawPosition(TRUE, NULL);
2727 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2729 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2730 #define STARTED_NONE 0
2731 #define STARTED_MOVES 1
2732 #define STARTED_BOARD 2
2733 #define STARTED_OBSERVE 3
2734 #define STARTED_HOLDINGS 4
2735 #define STARTED_CHATTER 5
2736 #define STARTED_COMMENT 6
2737 #define STARTED_MOVES_NOHIDE 7
2739 static int started = STARTED_NONE;
2740 static char parse[20000];
2741 static int parse_pos = 0;
2742 static char buf[BUF_SIZE + 1];
2743 static int firstTime = TRUE, intfSet = FALSE;
2744 static ColorClass prevColor = ColorNormal;
2745 static int savingComment = FALSE;
2746 static int cmatch = 0; // continuation sequence match
2753 int backup; /* [DM] For zippy color lines */
2755 char talker[MSG_SIZ]; // [HGM] chat
2758 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2760 if (appData.debugMode) {
2762 fprintf(debugFP, "<ICS: ");
2763 show_bytes(debugFP, data, count);
2764 fprintf(debugFP, "\n");
2768 if (appData.debugMode) { int f = forwardMostMove;
2769 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2770 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2771 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2774 /* If last read ended with a partial line that we couldn't parse,
2775 prepend it to the new read and try again. */
2776 if (leftover_len > 0) {
2777 for (i=0; i<leftover_len; i++)
2778 buf[i] = buf[leftover_start + i];
2781 /* copy new characters into the buffer */
2782 bp = buf + leftover_len;
2783 buf_len=leftover_len;
2784 for (i=0; i<count; i++)
2787 if (data[i] == '\r')
2790 // join lines split by ICS?
2791 if (!appData.noJoin)
2794 Joining just consists of finding matches against the
2795 continuation sequence, and discarding that sequence
2796 if found instead of copying it. So, until a match
2797 fails, there's nothing to do since it might be the
2798 complete sequence, and thus, something we don't want
2801 if (data[i] == cont_seq[cmatch])
2804 if (cmatch == strlen(cont_seq))
2806 cmatch = 0; // complete match. just reset the counter
2809 it's possible for the ICS to not include the space
2810 at the end of the last word, making our [correct]
2811 join operation fuse two separate words. the server
2812 does this when the space occurs at the width setting.
2814 if (!buf_len || buf[buf_len-1] != ' ')
2825 match failed, so we have to copy what matched before
2826 falling through and copying this character. In reality,
2827 this will only ever be just the newline character, but
2828 it doesn't hurt to be precise.
2830 strncpy(bp, cont_seq, cmatch);
2842 buf[buf_len] = NULLCHAR;
2843 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2848 while (i < buf_len) {
2849 /* Deal with part of the TELNET option negotiation
2850 protocol. We refuse to do anything beyond the
2851 defaults, except that we allow the WILL ECHO option,
2852 which ICS uses to turn off password echoing when we are
2853 directly connected to it. We reject this option
2854 if localLineEditing mode is on (always on in xboard)
2855 and we are talking to port 23, which might be a real
2856 telnet server that will try to keep WILL ECHO on permanently.
2858 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2859 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2860 unsigned char option;
2862 switch ((unsigned char) buf[++i]) {
2864 if (appData.debugMode)
2865 fprintf(debugFP, "\n<WILL ");
2866 switch (option = (unsigned char) buf[++i]) {
2868 if (appData.debugMode)
2869 fprintf(debugFP, "ECHO ");
2870 /* Reply only if this is a change, according
2871 to the protocol rules. */
2872 if (remoteEchoOption) break;
2873 if (appData.localLineEditing &&
2874 atoi(appData.icsPort) == TN_PORT) {
2875 TelnetRequest(TN_DONT, TN_ECHO);
2878 TelnetRequest(TN_DO, TN_ECHO);
2879 remoteEchoOption = TRUE;
2883 if (appData.debugMode)
2884 fprintf(debugFP, "%d ", option);
2885 /* Whatever this is, we don't want it. */
2886 TelnetRequest(TN_DONT, option);
2891 if (appData.debugMode)
2892 fprintf(debugFP, "\n<WONT ");
2893 switch (option = (unsigned char) buf[++i]) {
2895 if (appData.debugMode)
2896 fprintf(debugFP, "ECHO ");
2897 /* Reply only if this is a change, according
2898 to the protocol rules. */
2899 if (!remoteEchoOption) break;
2901 TelnetRequest(TN_DONT, TN_ECHO);
2902 remoteEchoOption = FALSE;
2905 if (appData.debugMode)
2906 fprintf(debugFP, "%d ", (unsigned char) option);
2907 /* Whatever this is, it must already be turned
2908 off, because we never agree to turn on
2909 anything non-default, so according to the
2910 protocol rules, we don't reply. */
2915 if (appData.debugMode)
2916 fprintf(debugFP, "\n<DO ");
2917 switch (option = (unsigned char) buf[++i]) {
2919 /* Whatever this is, we refuse to do it. */
2920 if (appData.debugMode)
2921 fprintf(debugFP, "%d ", option);
2922 TelnetRequest(TN_WONT, option);
2927 if (appData.debugMode)
2928 fprintf(debugFP, "\n<DONT ");
2929 switch (option = (unsigned char) buf[++i]) {
2931 if (appData.debugMode)
2932 fprintf(debugFP, "%d ", option);
2933 /* Whatever this is, we are already not doing
2934 it, because we never agree to do anything
2935 non-default, so according to the protocol
2936 rules, we don't reply. */
2941 if (appData.debugMode)
2942 fprintf(debugFP, "\n<IAC ");
2943 /* Doubled IAC; pass it through */
2947 if (appData.debugMode)
2948 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2949 /* Drop all other telnet commands on the floor */
2952 if (oldi > next_out)
2953 SendToPlayer(&buf[next_out], oldi - next_out);
2959 /* OK, this at least will *usually* work */
2960 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2964 if (loggedOn && !intfSet) {
2965 if (ics_type == ICS_ICC) {
2966 snprintf(str, MSG_SIZ,
2967 "/set-quietly interface %s\n/set-quietly style 12\n",
2969 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2970 strcat(str, "/set-2 51 1\n/set seek 1\n");
2971 } else if (ics_type == ICS_CHESSNET) {
2972 snprintf(str, MSG_SIZ, "/style 12\n");
2974 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2975 strcat(str, programVersion);
2976 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2977 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2978 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2980 strcat(str, "$iset nohighlight 1\n");
2982 strcat(str, "$iset lock 1\n$style 12\n");
2985 NotifyFrontendLogin();
2989 if (started == STARTED_COMMENT) {
2990 /* Accumulate characters in comment */
2991 parse[parse_pos++] = buf[i];
2992 if (buf[i] == '\n') {
2993 parse[parse_pos] = NULLCHAR;
2994 if(chattingPartner>=0) {
2996 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2997 OutputChatMessage(chattingPartner, mess);
2998 chattingPartner = -1;
2999 next_out = i+1; // [HGM] suppress printing in ICS window
3001 if(!suppressKibitz) // [HGM] kibitz
3002 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3003 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3004 int nrDigit = 0, nrAlph = 0, j;
3005 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3006 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3007 parse[parse_pos] = NULLCHAR;
3008 // try to be smart: if it does not look like search info, it should go to
3009 // ICS interaction window after all, not to engine-output window.
3010 for(j=0; j<parse_pos; j++) { // count letters and digits
3011 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3012 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3013 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3015 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3016 int depth=0; float score;
3017 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3018 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3019 pvInfoList[forwardMostMove-1].depth = depth;
3020 pvInfoList[forwardMostMove-1].score = 100*score;
3022 OutputKibitz(suppressKibitz, parse);
3025 if(gameMode == IcsObserving) // restore original ICS messages
3026 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3028 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3029 SendToPlayer(tmp, strlen(tmp));
3031 next_out = i+1; // [HGM] suppress printing in ICS window
3033 started = STARTED_NONE;
3035 /* Don't match patterns against characters in comment */
3040 if (started == STARTED_CHATTER) {
3041 if (buf[i] != '\n') {
3042 /* Don't match patterns against characters in chatter */
3046 started = STARTED_NONE;
3047 if(suppressKibitz) next_out = i+1;
3050 /* Kludge to deal with rcmd protocol */
3051 if (firstTime && looking_at(buf, &i, "\001*")) {
3052 DisplayFatalError(&buf[1], 0, 1);
3058 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3061 if (appData.debugMode)
3062 fprintf(debugFP, "ics_type %d\n", ics_type);
3065 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3066 ics_type = ICS_FICS;
3068 if (appData.debugMode)
3069 fprintf(debugFP, "ics_type %d\n", ics_type);
3072 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3073 ics_type = ICS_CHESSNET;
3075 if (appData.debugMode)
3076 fprintf(debugFP, "ics_type %d\n", ics_type);
3081 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3082 looking_at(buf, &i, "Logging you in as \"*\"") ||
3083 looking_at(buf, &i, "will be \"*\""))) {
3084 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3088 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3090 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3091 DisplayIcsInteractionTitle(buf);
3092 have_set_title = TRUE;
3095 /* skip finger notes */
3096 if (started == STARTED_NONE &&
3097 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3098 (buf[i] == '1' && buf[i+1] == '0')) &&
3099 buf[i+2] == ':' && buf[i+3] == ' ') {
3100 started = STARTED_CHATTER;
3106 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3107 if(appData.seekGraph) {
3108 if(soughtPending && MatchSoughtLine(buf+i)) {
3109 i = strstr(buf+i, "rated") - buf;
3110 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111 next_out = leftover_start = i;
3112 started = STARTED_CHATTER;
3113 suppressKibitz = TRUE;
3116 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3117 && looking_at(buf, &i, "* ads displayed")) {
3118 soughtPending = FALSE;
3123 if(appData.autoRefresh) {
3124 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3125 int s = (ics_type == ICS_ICC); // ICC format differs
3127 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3128 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3129 looking_at(buf, &i, "*% "); // eat prompt
3130 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3131 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132 next_out = i; // suppress
3135 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3136 char *p = star_match[0];
3138 if(seekGraphUp) RemoveSeekAd(atoi(p));
3139 while(*p && *p++ != ' '); // next
3141 looking_at(buf, &i, "*% "); // eat prompt
3142 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3149 /* skip formula vars */
3150 if (started == STARTED_NONE &&
3151 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3152 started = STARTED_CHATTER;
3157 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3158 if (appData.autoKibitz && started == STARTED_NONE &&
3159 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3160 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3161 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3162 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3163 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3164 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3165 suppressKibitz = TRUE;
3166 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3168 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3169 && (gameMode == IcsPlayingWhite)) ||
3170 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3171 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3172 started = STARTED_CHATTER; // own kibitz we simply discard
3174 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3175 parse_pos = 0; parse[0] = NULLCHAR;
3176 savingComment = TRUE;
3177 suppressKibitz = gameMode != IcsObserving ? 2 :
3178 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3182 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3183 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3184 && atoi(star_match[0])) {
3185 // suppress the acknowledgements of our own autoKibitz
3187 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3188 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3189 SendToPlayer(star_match[0], strlen(star_match[0]));
3190 if(looking_at(buf, &i, "*% ")) // eat prompt
3191 suppressKibitz = FALSE;
3195 } // [HGM] kibitz: end of patch
3197 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3199 // [HGM] chat: intercept tells by users for which we have an open chat window
3201 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3202 looking_at(buf, &i, "* whispers:") ||
3203 looking_at(buf, &i, "* kibitzes:") ||
3204 looking_at(buf, &i, "* shouts:") ||
3205 looking_at(buf, &i, "* c-shouts:") ||
3206 looking_at(buf, &i, "--> * ") ||
3207 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3208 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3209 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3210 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3212 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3213 chattingPartner = -1;
3215 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3216 for(p=0; p<MAX_CHAT; p++) {
3217 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3218 talker[0] = '['; strcat(talker, "] ");
3219 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3220 chattingPartner = p; break;
3223 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3224 for(p=0; p<MAX_CHAT; p++) {
3225 if(!strcmp("kibitzes", chatPartner[p])) {
3226 talker[0] = '['; strcat(talker, "] ");
3227 chattingPartner = p; break;
3230 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3231 for(p=0; p<MAX_CHAT; p++) {
3232 if(!strcmp("whispers", chatPartner[p])) {
3233 talker[0] = '['; strcat(talker, "] ");
3234 chattingPartner = p; break;
3237 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3238 if(buf[i-8] == '-' && buf[i-3] == 't')
3239 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3240 if(!strcmp("c-shouts", chatPartner[p])) {
3241 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3242 chattingPartner = p; break;
3245 if(chattingPartner < 0)
3246 for(p=0; p<MAX_CHAT; p++) {
3247 if(!strcmp("shouts", chatPartner[p])) {
3248 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3249 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3250 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3251 chattingPartner = p; break;
3255 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3256 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3257 talker[0] = 0; Colorize(ColorTell, FALSE);
3258 chattingPartner = p; break;
3260 if(chattingPartner<0) i = oldi; else {
3261 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3262 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3263 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3264 started = STARTED_COMMENT;
3265 parse_pos = 0; parse[0] = NULLCHAR;
3266 savingComment = 3 + chattingPartner; // counts as TRUE
3267 suppressKibitz = TRUE;
3270 } // [HGM] chat: end of patch
3273 if (appData.zippyTalk || appData.zippyPlay) {
3274 /* [DM] Backup address for color zippy lines */
3276 if (loggedOn == TRUE)
3277 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3278 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3280 } // [DM] 'else { ' deleted
3282 /* Regular tells and says */
3283 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3284 looking_at(buf, &i, "* (your partner) tells you: ") ||
3285 looking_at(buf, &i, "* says: ") ||
3286 /* Don't color "message" or "messages" output */
3287 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3288 looking_at(buf, &i, "*. * at *:*: ") ||
3289 looking_at(buf, &i, "--* (*:*): ") ||
3290 /* Message notifications (same color as tells) */
3291 looking_at(buf, &i, "* has left a message ") ||
3292 looking_at(buf, &i, "* just sent you a message:\n") ||
3293 /* Whispers and kibitzes */
3294 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3295 looking_at(buf, &i, "* kibitzes: ") ||
3297 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3299 if (tkind == 1 && strchr(star_match[0], ':')) {
3300 /* Avoid "tells you:" spoofs in channels */
3303 if (star_match[0][0] == NULLCHAR ||
3304 strchr(star_match[0], ' ') ||
3305 (tkind == 3 && strchr(star_match[1], ' '))) {
3306 /* Reject bogus matches */
3309 if (appData.colorize) {
3310 if (oldi > next_out) {
3311 SendToPlayer(&buf[next_out], oldi - next_out);
3316 Colorize(ColorTell, FALSE);
3317 curColor = ColorTell;
3320 Colorize(ColorKibitz, FALSE);
3321 curColor = ColorKibitz;
3324 p = strrchr(star_match[1], '(');
3331 Colorize(ColorChannel1, FALSE);
3332 curColor = ColorChannel1;
3334 Colorize(ColorChannel, FALSE);
3335 curColor = ColorChannel;
3339 curColor = ColorNormal;
3343 if (started == STARTED_NONE && appData.autoComment &&
3344 (gameMode == IcsObserving ||
3345 gameMode == IcsPlayingWhite ||
3346 gameMode == IcsPlayingBlack)) {
3347 parse_pos = i - oldi;
3348 memcpy(parse, &buf[oldi], parse_pos);
3349 parse[parse_pos] = NULLCHAR;
3350 started = STARTED_COMMENT;
3351 savingComment = TRUE;
3353 started = STARTED_CHATTER;
3354 savingComment = FALSE;
3361 if (looking_at(buf, &i, "* s-shouts: ") ||
3362 looking_at(buf, &i, "* c-shouts: ")) {
3363 if (appData.colorize) {
3364 if (oldi > next_out) {
3365 SendToPlayer(&buf[next_out], oldi - next_out);
3368 Colorize(ColorSShout, FALSE);
3369 curColor = ColorSShout;
3372 started = STARTED_CHATTER;
3376 if (looking_at(buf, &i, "--->")) {
3381 if (looking_at(buf, &i, "* shouts: ") ||
3382 looking_at(buf, &i, "--> ")) {
3383 if (appData.colorize) {
3384 if (oldi > next_out) {
3385 SendToPlayer(&buf[next_out], oldi - next_out);
3388 Colorize(ColorShout, FALSE);
3389 curColor = ColorShout;
3392 started = STARTED_CHATTER;
3396 if (looking_at( buf, &i, "Challenge:")) {
3397 if (appData.colorize) {
3398 if (oldi > next_out) {
3399 SendToPlayer(&buf[next_out], oldi - next_out);
3402 Colorize(ColorChallenge, FALSE);
3403 curColor = ColorChallenge;
3409 if (looking_at(buf, &i, "* offers you") ||
3410 looking_at(buf, &i, "* offers to be") ||
3411 looking_at(buf, &i, "* would like to") ||
3412 looking_at(buf, &i, "* requests to") ||
3413 looking_at(buf, &i, "Your opponent offers") ||
3414 looking_at(buf, &i, "Your opponent requests")) {
3416 if (appData.colorize) {
3417 if (oldi > next_out) {
3418 SendToPlayer(&buf[next_out], oldi - next_out);