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));
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
253 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
277 /* States for ics_getting_history */
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
285 /* whosays values for GameEnds */
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
297 /* Different types of move when calling RegisterMove */
299 #define CMAIL_RESIGN 1
301 #define CMAIL_ACCEPT 3
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
308 /* Telnet protocol constants */
319 safeStrCpy (char *dst, const char *src, size_t count)
322 assert( dst != NULL );
323 assert( src != NULL );
326 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327 if( i == count && dst[count-1] != NULLCHAR)
329 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330 if(appData.debugMode)
331 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
337 /* Some compiler can't cast u64 to double
338 * This function do the job for us:
340 * We use the highest bit for cast, this only
341 * works if the highest bit is not
342 * in use (This should not happen)
344 * We used this for all compiler
347 u64ToDouble (u64 value)
350 u64 tmp = value & u64Const(0x7fffffffffffffff);
351 r = (double)(s64)tmp;
352 if (value & u64Const(0x8000000000000000))
353 r += 9.2233720368547758080e18; /* 2^63 */
357 /* Fake up flags for now, as we aren't keeping track of castling
358 availability yet. [HGM] Change of logic: the flag now only
359 indicates the type of castlings allowed by the rule of the game.
360 The actual rights themselves are maintained in the array
361 castlingRights, as part of the game history, and are not probed
367 int flags = F_ALL_CASTLE_OK;
368 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369 switch (gameInfo.variant) {
371 flags &= ~F_ALL_CASTLE_OK;
372 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373 flags |= F_IGNORE_CHECK;
375 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380 case VariantKriegspiel:
381 flags |= F_KRIEGSPIEL_CAPTURE;
383 case VariantCapaRandom:
384 case VariantFischeRandom:
385 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386 case VariantNoCastle:
387 case VariantShatranj:
391 flags &= ~F_ALL_CASTLE_OK;
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
403 [AS] Note: sometimes, the sscanf() function is used to parse the input
404 into a fixed-size buffer. Because of this, we must be prepared to
405 receive strings as long as the size of the input buffer, which is currently
406 set to 4K for Windows and 8K for the rest.
407 So, we must either allocate sufficiently large buffers here, or
408 reduce the size of the input buffer in the input reading part.
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
415 ChessProgramState first, second, pairing;
417 /* premove variables */
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
458 int have_sent_ICS_logon = 0;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE, startingEngine = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
472 /* animateTraining preserves the state of appData.animate
473 * when Training mode is activated. This allows the
474 * response to be animated when appData.animate == TRUE and
475 * appData.animateDragging == TRUE.
477 Boolean animateTraining;
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char initialRights[BOARD_FILES];
487 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int initialRulePlies, FENrulePlies;
489 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
510 ChessSquare FIDEArray[2][BOARD_FILES] = {
511 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514 BlackKing, BlackBishop, BlackKnight, BlackRook }
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521 BlackKing, BlackKing, BlackKnight, BlackRook }
524 ChessSquare KnightmateArray[2][BOARD_FILES] = {
525 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527 { BlackRook, BlackMan, BlackBishop, BlackQueen,
528 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackMan, BlackFerz,
556 BlackKing, BlackMan, BlackKnight, BlackRook }
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
611 #define GothicArray CapablancaArray
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
622 #define FalconArray CapablancaArray
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
644 Board initialPosition;
647 /* Convert str to a rating. Checks for special cases of "----",
649 "++++", etc. Also strips ()'s */
651 string_to_rating (char *str)
653 while(*str && !isdigit(*str)) ++str;
655 return 0; /* One of the special "no rating" cases */
663 /* Init programStats */
664 programStats.movelist[0] = 0;
665 programStats.depth = 0;
666 programStats.nr_moves = 0;
667 programStats.moves_left = 0;
668 programStats.nodes = 0;
669 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
670 programStats.score = 0;
671 programStats.got_only_move = 0;
672 programStats.got_fail = 0;
673 programStats.line_is_book = 0;
678 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679 if (appData.firstPlaysBlack) {
680 first.twoMachinesColor = "black\n";
681 second.twoMachinesColor = "white\n";
683 first.twoMachinesColor = "white\n";
684 second.twoMachinesColor = "black\n";
687 first.other = &second;
688 second.other = &first;
691 if(appData.timeOddsMode) {
692 norm = appData.timeOdds[0];
693 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
695 first.timeOdds = appData.timeOdds[0]/norm;
696 second.timeOdds = appData.timeOdds[1]/norm;
699 if(programVersion) free(programVersion);
700 if (appData.noChessProgram) {
701 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702 sprintf(programVersion, "%s", PACKAGE_STRING);
704 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
711 UnloadEngine (ChessProgramState *cps)
713 /* Kill off first chess program */
714 if (cps->isr != NULL)
715 RemoveInputSource(cps->isr);
718 if (cps->pr != NoProc) {
720 DoSleep( appData.delayBeforeQuit );
721 SendToProgram("quit\n", cps);
722 DoSleep( appData.delayAfterQuit );
723 DestroyChildProcess(cps->pr, cps->useSigterm);
726 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
730 ClearOptions (ChessProgramState *cps)
733 cps->nrOptions = cps->comboCnt = 0;
734 for(i=0; i<MAX_OPTIONS; i++) {
735 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736 cps->option[i].textValue = 0;
740 char *engineNames[] = {
741 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
750 InitEngine (ChessProgramState *cps, int n)
751 { // [HGM] all engine initialiation put in a function that does one engine
755 cps->which = engineNames[n];
756 cps->maybeThinking = FALSE;
760 cps->sendDrawOffers = 1;
762 cps->program = appData.chessProgram[n];
763 cps->host = appData.host[n];
764 cps->dir = appData.directory[n];
765 cps->initString = appData.engInitString[n];
766 cps->computerString = appData.computerString[n];
767 cps->useSigint = TRUE;
768 cps->useSigterm = TRUE;
769 cps->reuse = appData.reuse[n];
770 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
771 cps->useSetboard = FALSE;
773 cps->usePing = FALSE;
776 cps->usePlayother = FALSE;
777 cps->useColors = TRUE;
778 cps->useUsermove = FALSE;
779 cps->sendICS = FALSE;
780 cps->sendName = appData.icsActive;
781 cps->sdKludge = FALSE;
782 cps->stKludge = FALSE;
783 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
784 TidyProgramName(cps->program, cps->host, cps->tidy);
786 ASSIGN(cps->variants, appData.variant);
787 cps->analysisSupport = 2; /* detect */
788 cps->analyzing = FALSE;
789 cps->initDone = FALSE;
792 /* New features added by Tord: */
793 cps->useFEN960 = FALSE;
794 cps->useOOCastle = TRUE;
795 /* End of new features added by Tord. */
796 cps->fenOverride = appData.fenOverride[n];
798 /* [HGM] time odds: set factor for each machine */
799 cps->timeOdds = appData.timeOdds[n];
801 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802 cps->accumulateTC = appData.accumulateTC[n];
803 cps->maxNrOfSessions = 1;
808 cps->supportsNPS = UNKNOWN;
809 cps->memSize = FALSE;
810 cps->maxCores = FALSE;
811 ASSIGN(cps->egtFormats, "");
814 cps->optionSettings = appData.engOptions[n];
816 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817 cps->isUCI = appData.isUCI[n]; /* [AS] */
818 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
820 if (appData.protocolVersion[n] > PROTOVER
821 || appData.protocolVersion[n] < 1)
826 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827 appData.protocolVersion[n]);
828 if( (len >= MSG_SIZ) && appData.debugMode )
829 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
831 DisplayFatalError(buf, 0, 2);
835 cps->protocolVersion = appData.protocolVersion[n];
838 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
839 ParseFeatures(appData.featureDefaults, cps);
842 ChessProgramState *savCps;
850 if(WaitForEngine(savCps, LoadEngine)) return;
851 CommonEngineInit(); // recalculate time odds
852 if(gameInfo.variant != StringToVariant(appData.variant)) {
853 // we changed variant when loading the engine; this forces us to reset
854 Reset(TRUE, savCps != &first);
855 oldMode = BeginningOfGame; // to prevent restoring old mode
857 InitChessProgram(savCps, FALSE);
858 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
859 DisplayMessage("", "");
860 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
861 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
864 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
868 ReplaceEngine (ChessProgramState *cps, int n)
870 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
872 if(oldMode != BeginningOfGame) EditGameEvent();
875 appData.noChessProgram = FALSE;
876 appData.clockMode = TRUE;
879 if(n) return; // only startup first engine immediately; second can wait
880 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
884 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
885 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
887 static char resetOptions[] =
888 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
889 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
890 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
891 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
894 FloatToFront(char **list, char *engineLine)
896 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
898 if(appData.recentEngines <= 0) return;
899 TidyProgramName(engineLine, "localhost", tidy+1);
900 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
901 strncpy(buf+1, *list, MSG_SIZ-50);
902 if(p = strstr(buf, tidy)) { // tidy name appears in list
903 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
904 while(*p++ = *++q); // squeeze out
906 strcat(tidy, buf+1); // put list behind tidy name
907 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
908 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
909 ASSIGN(*list, tidy+1);
912 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
915 Load (ChessProgramState *cps, int i)
917 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
918 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
919 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
920 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
921 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
922 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
923 appData.firstProtocolVersion = PROTOVER;
924 ParseArgsFromString(buf);
926 ReplaceEngine(cps, i);
927 FloatToFront(&appData.recentEngineList, engineLine);
931 while(q = strchr(p, SLASH)) p = q+1;
932 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
933 if(engineDir[0] != NULLCHAR) {
934 ASSIGN(appData.directory[i], engineDir); p = engineName;
935 } else if(p != engineName) { // derive directory from engine path, when not given
937 ASSIGN(appData.directory[i], engineName);
939 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
940 } else { ASSIGN(appData.directory[i], "."); }
942 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
943 snprintf(command, MSG_SIZ, "%s %s", p, params);
946 ASSIGN(appData.chessProgram[i], p);
947 appData.isUCI[i] = isUCI;
948 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
949 appData.hasOwnBookUCI[i] = hasBook;
950 if(!nickName[0]) useNick = FALSE;
951 if(useNick) ASSIGN(appData.pgnName[i], nickName);
955 q = firstChessProgramNames;
956 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
957 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
958 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
959 quote, p, quote, appData.directory[i],
960 useNick ? " -fn \"" : "",
961 useNick ? nickName : "",
963 v1 ? " -firstProtocolVersion 1" : "",
964 hasBook ? "" : " -fNoOwnBookUCI",
965 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
966 storeVariant ? " -variant " : "",
967 storeVariant ? VariantName(gameInfo.variant) : "");
968 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
969 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
970 if(insert != q) insert[-1] = NULLCHAR;
971 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
973 FloatToFront(&appData.recentEngineList, buf);
975 ReplaceEngine(cps, i);
981 int matched, min, sec;
983 * Parse timeControl resource
985 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
986 appData.movesPerSession)) {
988 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
989 DisplayFatalError(buf, 0, 2);
993 * Parse searchTime resource
995 if (*appData.searchTime != NULLCHAR) {
996 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
998 searchTime = min * 60;
999 } else if (matched == 2) {
1000 searchTime = min * 60 + sec;
1003 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1004 DisplayFatalError(buf, 0, 2);
1013 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1014 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1016 GetTimeMark(&programStartTime);
1017 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1018 appData.seedBase = random() + (random()<<15);
1019 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1021 ClearProgramStats();
1022 programStats.ok_to_send = 1;
1023 programStats.seen_stat = 0;
1026 * Initialize game list
1032 * Internet chess server status
1034 if (appData.icsActive) {
1035 appData.matchMode = FALSE;
1036 appData.matchGames = 0;
1038 appData.noChessProgram = !appData.zippyPlay;
1040 appData.zippyPlay = FALSE;
1041 appData.zippyTalk = FALSE;
1042 appData.noChessProgram = TRUE;
1044 if (*appData.icsHelper != NULLCHAR) {
1045 appData.useTelnet = TRUE;
1046 appData.telnetProgram = appData.icsHelper;
1049 appData.zippyTalk = appData.zippyPlay = FALSE;
1052 /* [AS] Initialize pv info list [HGM] and game state */
1056 for( i=0; i<=framePtr; i++ ) {
1057 pvInfoList[i].depth = -1;
1058 boards[i][EP_STATUS] = EP_NONE;
1059 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1065 /* [AS] Adjudication threshold */
1066 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1068 InitEngine(&first, 0);
1069 InitEngine(&second, 1);
1072 pairing.which = "pairing"; // pairing engine
1073 pairing.pr = NoProc;
1075 pairing.program = appData.pairingEngine;
1076 pairing.host = "localhost";
1079 if (appData.icsActive) {
1080 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1081 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1082 appData.clockMode = FALSE;
1083 first.sendTime = second.sendTime = 0;
1087 /* Override some settings from environment variables, for backward
1088 compatibility. Unfortunately it's not feasible to have the env
1089 vars just set defaults, at least in xboard. Ugh.
1091 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1096 if (!appData.icsActive) {
1100 /* Check for variants that are supported only in ICS mode,
1101 or not at all. Some that are accepted here nevertheless
1102 have bugs; see comments below.
1104 VariantClass variant = StringToVariant(appData.variant);
1106 case VariantBughouse: /* need four players and two boards */
1107 case VariantKriegspiel: /* need to hide pieces and move details */
1108 /* case VariantFischeRandom: (Fabien: moved below) */
1109 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1110 if( (len >= MSG_SIZ) && appData.debugMode )
1111 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1113 DisplayFatalError(buf, 0, 2);
1116 case VariantUnknown:
1117 case VariantLoadable:
1127 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1128 if( (len >= MSG_SIZ) && appData.debugMode )
1129 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1131 DisplayFatalError(buf, 0, 2);
1134 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1135 case VariantFairy: /* [HGM] TestLegality definitely off! */
1136 case VariantGothic: /* [HGM] should work */
1137 case VariantCapablanca: /* [HGM] should work */
1138 case VariantCourier: /* [HGM] initial forced moves not implemented */
1139 case VariantShogi: /* [HGM] could still mate with pawn drop */
1140 case VariantKnightmate: /* [HGM] should work */
1141 case VariantCylinder: /* [HGM] untested */
1142 case VariantFalcon: /* [HGM] untested */
1143 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1144 offboard interposition not understood */
1145 case VariantNormal: /* definitely works! */
1146 case VariantWildCastle: /* pieces not automatically shuffled */
1147 case VariantNoCastle: /* pieces not automatically shuffled */
1148 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1149 case VariantLosers: /* should work except for win condition,
1150 and doesn't know captures are mandatory */
1151 case VariantSuicide: /* should work except for win condition,
1152 and doesn't know captures are mandatory */
1153 case VariantGiveaway: /* should work except for win condition,
1154 and doesn't know captures are mandatory */
1155 case VariantTwoKings: /* should work */
1156 case VariantAtomic: /* should work except for win condition */
1157 case Variant3Check: /* should work except for win condition */
1158 case VariantShatranj: /* should work except for all win conditions */
1159 case VariantMakruk: /* should work except for draw countdown */
1160 case VariantBerolina: /* might work if TestLegality is off */
1161 case VariantCapaRandom: /* should work */
1162 case VariantJanus: /* should work */
1163 case VariantSuper: /* experimental */
1164 case VariantGreat: /* experimental, requires legality testing to be off */
1165 case VariantSChess: /* S-Chess, should work */
1166 case VariantGrand: /* should work */
1167 case VariantSpartan: /* should work */
1175 NextIntegerFromString (char ** str, long * value)
1180 while( *s == ' ' || *s == '\t' ) {
1186 if( *s >= '0' && *s <= '9' ) {
1187 while( *s >= '0' && *s <= '9' ) {
1188 *value = *value * 10 + (*s - '0');
1201 NextTimeControlFromString (char ** str, long * value)
1204 int result = NextIntegerFromString( str, &temp );
1207 *value = temp * 60; /* Minutes */
1208 if( **str == ':' ) {
1210 result = NextIntegerFromString( str, &temp );
1211 *value += temp; /* Seconds */
1219 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1220 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1221 int result = -1, type = 0; long temp, temp2;
1223 if(**str != ':') return -1; // old params remain in force!
1225 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1226 if( NextIntegerFromString( str, &temp ) ) return -1;
1227 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1230 /* time only: incremental or sudden-death time control */
1231 if(**str == '+') { /* increment follows; read it */
1233 if(**str == '!') type = *(*str)++; // Bronstein TC
1234 if(result = NextIntegerFromString( str, &temp2)) return -1;
1235 *inc = temp2 * 1000;
1236 if(**str == '.') { // read fraction of increment
1237 char *start = ++(*str);
1238 if(result = NextIntegerFromString( str, &temp2)) return -1;
1240 while(start++ < *str) temp2 /= 10;
1244 *moves = 0; *tc = temp * 1000; *incType = type;
1248 (*str)++; /* classical time control */
1249 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1261 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1262 { /* [HGM] get time to add from the multi-session time-control string */
1263 int incType, moves=1; /* kludge to force reading of first session */
1264 long time, increment;
1267 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1269 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1270 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1271 if(movenr == -1) return time; /* last move before new session */
1272 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1273 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1274 if(!moves) return increment; /* current session is incremental */
1275 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1276 } while(movenr >= -1); /* try again for next session */
1278 return 0; // no new time quota on this move
1282 ParseTimeControl (char *tc, float ti, int mps)
1286 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1289 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1290 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1291 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1295 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1297 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1300 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1302 snprintf(buf, MSG_SIZ, ":%s", mytc);
1304 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1306 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1311 /* Parse second time control */
1314 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1322 timeControl_2 = tc2 * 1000;
1332 timeControl = tc1 * 1000;
1335 timeIncrement = ti * 1000; /* convert to ms */
1336 movesPerSession = 0;
1339 movesPerSession = mps;
1347 if (appData.debugMode) {
1348 # ifdef __GIT_VERSION
1349 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1351 fprintf(debugFP, "Version: %s\n", programVersion);
1354 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1356 set_cont_sequence(appData.wrapContSeq);
1357 if (appData.matchGames > 0) {
1358 appData.matchMode = TRUE;
1359 } else if (appData.matchMode) {
1360 appData.matchGames = 1;
1362 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1363 appData.matchGames = appData.sameColorGames;
1364 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1365 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1366 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1369 if (appData.noChessProgram || first.protocolVersion == 1) {
1372 /* kludge: allow timeout for initial "feature" commands */
1374 DisplayMessage("", _("Starting chess program"));
1375 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1380 CalculateIndex (int index, int gameNr)
1381 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1383 if(index > 0) return index; // fixed nmber
1384 if(index == 0) return 1;
1385 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1386 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1391 LoadGameOrPosition (int gameNr)
1392 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1393 if (*appData.loadGameFile != NULLCHAR) {
1394 if (!LoadGameFromFile(appData.loadGameFile,
1395 CalculateIndex(appData.loadGameIndex, gameNr),
1396 appData.loadGameFile, FALSE)) {
1397 DisplayFatalError(_("Bad game file"), 0, 1);
1400 } else if (*appData.loadPositionFile != NULLCHAR) {
1401 if (!LoadPositionFromFile(appData.loadPositionFile,
1402 CalculateIndex(appData.loadPositionIndex, gameNr),
1403 appData.loadPositionFile)) {
1404 DisplayFatalError(_("Bad position file"), 0, 1);
1412 ReserveGame (int gameNr, char resChar)
1414 FILE *tf = fopen(appData.tourneyFile, "r+");
1415 char *p, *q, c, buf[MSG_SIZ];
1416 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1417 safeStrCpy(buf, lastMsg, MSG_SIZ);
1418 DisplayMessage(_("Pick new game"), "");
1419 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1420 ParseArgsFromFile(tf);
1421 p = q = appData.results;
1422 if(appData.debugMode) {
1423 char *r = appData.participants;
1424 fprintf(debugFP, "results = '%s'\n", p);
1425 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1426 fprintf(debugFP, "\n");
1428 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1430 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1431 safeStrCpy(q, p, strlen(p) + 2);
1432 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1433 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1434 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1435 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1438 fseek(tf, -(strlen(p)+4), SEEK_END);
1440 if(c != '"') // depending on DOS or Unix line endings we can be one off
1441 fseek(tf, -(strlen(p)+2), SEEK_END);
1442 else fseek(tf, -(strlen(p)+3), SEEK_END);
1443 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1444 DisplayMessage(buf, "");
1445 free(p); appData.results = q;
1446 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1447 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1448 int round = appData.defaultMatchGames * appData.tourneyType;
1449 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1450 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1451 UnloadEngine(&first); // next game belongs to other pairing;
1452 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1454 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1458 MatchEvent (int mode)
1459 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1461 if(matchMode) { // already in match mode: switch it off
1463 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1466 // if(gameMode != BeginningOfGame) {
1467 // DisplayError(_("You can only start a match from the initial position."), 0);
1471 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1472 /* Set up machine vs. machine match */
1474 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1475 if(appData.tourneyFile[0]) {
1477 if(nextGame > appData.matchGames) {
1479 if(strchr(appData.results, '*') == NULL) {
1481 appData.tourneyCycles++;
1482 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1484 NextTourneyGame(-1, &dummy);
1486 if(nextGame <= appData.matchGames) {
1487 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1489 ScheduleDelayedEvent(NextMatchGame, 10000);
1494 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1495 DisplayError(buf, 0);
1496 appData.tourneyFile[0] = 0;
1500 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1501 DisplayFatalError(_("Can't have a match with no chess programs"),
1506 matchGame = roundNr = 1;
1507 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1511 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1514 InitBackEnd3 P((void))
1516 GameMode initialMode;
1520 InitChessProgram(&first, startedFromSetupPosition);
1522 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1523 free(programVersion);
1524 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1525 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1526 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1529 if (appData.icsActive) {
1531 /* [DM] Make a console window if needed [HGM] merged ifs */
1537 if (*appData.icsCommPort != NULLCHAR)
1538 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1539 appData.icsCommPort);
1541 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1542 appData.icsHost, appData.icsPort);
1544 if( (len >= MSG_SIZ) && appData.debugMode )
1545 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1547 DisplayFatalError(buf, err, 1);
1552 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1554 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1555 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1556 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 } else if (appData.noChessProgram) {
1563 if (*appData.cmailGameName != NULLCHAR) {
1565 OpenLoopback(&cmailPR);
1567 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1571 DisplayMessage("", "");
1572 if (StrCaseCmp(appData.initialMode, "") == 0) {
1573 initialMode = BeginningOfGame;
1574 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1575 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1576 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1577 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1580 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1581 initialMode = TwoMachinesPlay;
1582 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1583 initialMode = AnalyzeFile;
1584 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1585 initialMode = AnalyzeMode;
1586 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1587 initialMode = MachinePlaysWhite;
1588 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1589 initialMode = MachinePlaysBlack;
1590 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1591 initialMode = EditGame;
1592 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1593 initialMode = EditPosition;
1594 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1595 initialMode = Training;
1597 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1598 if( (len >= MSG_SIZ) && appData.debugMode )
1599 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1601 DisplayFatalError(buf, 0, 2);
1605 if (appData.matchMode) {
1606 if(appData.tourneyFile[0]) { // start tourney from command line
1608 if(f = fopen(appData.tourneyFile, "r")) {
1609 ParseArgsFromFile(f); // make sure tourney parmeters re known
1611 appData.clockMode = TRUE;
1613 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1616 } else if (*appData.cmailGameName != NULLCHAR) {
1617 /* Set up cmail mode */
1618 ReloadCmailMsgEvent(TRUE);
1620 /* Set up other modes */
1621 if (initialMode == AnalyzeFile) {
1622 if (*appData.loadGameFile == NULLCHAR) {
1623 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1627 if (*appData.loadGameFile != NULLCHAR) {
1628 (void) LoadGameFromFile(appData.loadGameFile,
1629 appData.loadGameIndex,
1630 appData.loadGameFile, TRUE);
1631 } else if (*appData.loadPositionFile != NULLCHAR) {
1632 (void) LoadPositionFromFile(appData.loadPositionFile,
1633 appData.loadPositionIndex,
1634 appData.loadPositionFile);
1635 /* [HGM] try to make self-starting even after FEN load */
1636 /* to allow automatic setup of fairy variants with wtm */
1637 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1638 gameMode = BeginningOfGame;
1639 setboardSpoiledMachineBlack = 1;
1641 /* [HGM] loadPos: make that every new game uses the setup */
1642 /* from file as long as we do not switch variant */
1643 if(!blackPlaysFirst) {
1644 startedFromPositionFile = TRUE;
1645 CopyBoard(filePosition, boards[0]);
1648 if (initialMode == AnalyzeMode) {
1649 if (appData.noChessProgram) {
1650 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1653 if (appData.icsActive) {
1654 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1658 } else if (initialMode == AnalyzeFile) {
1659 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1660 ShowThinkingEvent();
1662 AnalysisPeriodicEvent(1);
1663 } else if (initialMode == MachinePlaysWhite) {
1664 if (appData.noChessProgram) {
1665 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1669 if (appData.icsActive) {
1670 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1674 MachineWhiteEvent();
1675 } else if (initialMode == MachinePlaysBlack) {
1676 if (appData.noChessProgram) {
1677 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1681 if (appData.icsActive) {
1682 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1686 MachineBlackEvent();
1687 } else if (initialMode == TwoMachinesPlay) {
1688 if (appData.noChessProgram) {
1689 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1693 if (appData.icsActive) {
1694 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1699 } else if (initialMode == EditGame) {
1701 } else if (initialMode == EditPosition) {
1702 EditPositionEvent();
1703 } else if (initialMode == Training) {
1704 if (*appData.loadGameFile == NULLCHAR) {
1705 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1714 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1716 DisplayBook(current+1);
1718 MoveHistorySet( movelist, first, last, current, pvInfoList );
1720 EvalGraphSet( first, last, current, pvInfoList );
1722 MakeEngineOutputTitle();
1726 * Establish will establish a contact to a remote host.port.
1727 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1728 * used to talk to the host.
1729 * Returns 0 if okay, error code if not.
1736 if (*appData.icsCommPort != NULLCHAR) {
1737 /* Talk to the host through a serial comm port */
1738 return OpenCommPort(appData.icsCommPort, &icsPR);
1740 } else if (*appData.gateway != NULLCHAR) {
1741 if (*appData.remoteShell == NULLCHAR) {
1742 /* Use the rcmd protocol to run telnet program on a gateway host */
1743 snprintf(buf, sizeof(buf), "%s %s %s",
1744 appData.telnetProgram, appData.icsHost, appData.icsPort);
1745 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1748 /* Use the rsh program to run telnet program on a gateway host */
1749 if (*appData.remoteUser == NULLCHAR) {
1750 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1751 appData.gateway, appData.telnetProgram,
1752 appData.icsHost, appData.icsPort);
1754 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1755 appData.remoteShell, appData.gateway,
1756 appData.remoteUser, appData.telnetProgram,
1757 appData.icsHost, appData.icsPort);
1759 return StartChildProcess(buf, "", &icsPR);
1762 } else if (appData.useTelnet) {
1763 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1766 /* TCP socket interface differs somewhat between
1767 Unix and NT; handle details in the front end.
1769 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1774 EscapeExpand (char *p, char *q)
1775 { // [HGM] initstring: routine to shape up string arguments
1776 while(*p++ = *q++) if(p[-1] == '\\')
1778 case 'n': p[-1] = '\n'; break;
1779 case 'r': p[-1] = '\r'; break;
1780 case 't': p[-1] = '\t'; break;
1781 case '\\': p[-1] = '\\'; break;
1782 case 0: *p = 0; return;
1783 default: p[-1] = q[-1]; break;
1788 show_bytes (FILE *fp, char *buf, int count)
1791 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1792 fprintf(fp, "\\%03o", *buf & 0xff);
1801 /* Returns an errno value */
1803 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1805 char buf[8192], *p, *q, *buflim;
1806 int left, newcount, outcount;
1808 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1809 *appData.gateway != NULLCHAR) {
1810 if (appData.debugMode) {
1811 fprintf(debugFP, ">ICS: ");
1812 show_bytes(debugFP, message, count);
1813 fprintf(debugFP, "\n");
1815 return OutputToProcess(pr, message, count, outError);
1818 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1825 if (appData.debugMode) {
1826 fprintf(debugFP, ">ICS: ");
1827 show_bytes(debugFP, buf, newcount);
1828 fprintf(debugFP, "\n");
1830 outcount = OutputToProcess(pr, buf, newcount, outError);
1831 if (outcount < newcount) return -1; /* to be sure */
1838 } else if (((unsigned char) *p) == TN_IAC) {
1839 *q++ = (char) TN_IAC;
1846 if (appData.debugMode) {
1847 fprintf(debugFP, ">ICS: ");
1848 show_bytes(debugFP, buf, newcount);
1849 fprintf(debugFP, "\n");
1851 outcount = OutputToProcess(pr, buf, newcount, outError);
1852 if (outcount < newcount) return -1; /* to be sure */
1857 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1859 int outError, outCount;
1860 static int gotEof = 0;
1863 /* Pass data read from player on to ICS */
1866 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1867 if (outCount < count) {
1868 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1870 if(have_sent_ICS_logon == 2) {
1871 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1872 fprintf(ini, "%s", message);
1873 have_sent_ICS_logon = 3;
1875 have_sent_ICS_logon = 1;
1876 } else if(have_sent_ICS_logon == 3) {
1877 fprintf(ini, "%s", message);
1879 have_sent_ICS_logon = 1;
1881 } else if (count < 0) {
1882 RemoveInputSource(isr);
1883 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1884 } else if (gotEof++ > 0) {
1885 RemoveInputSource(isr);
1886 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1892 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1893 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1894 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1895 SendToICS("date\n");
1896 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1899 /* added routine for printf style output to ics */
1901 ics_printf (char *format, ...)
1903 char buffer[MSG_SIZ];
1906 va_start(args, format);
1907 vsnprintf(buffer, sizeof(buffer), format, args);
1908 buffer[sizeof(buffer)-1] = '\0';
1916 int count, outCount, outError;
1918 if (icsPR == NoProc) return;
1921 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1922 if (outCount < count) {
1923 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1927 /* This is used for sending logon scripts to the ICS. Sending
1928 without a delay causes problems when using timestamp on ICC
1929 (at least on my machine). */
1931 SendToICSDelayed (char *s, long msdelay)
1933 int count, outCount, outError;
1935 if (icsPR == NoProc) return;
1938 if (appData.debugMode) {
1939 fprintf(debugFP, ">ICS: ");
1940 show_bytes(debugFP, s, count);
1941 fprintf(debugFP, "\n");
1943 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1945 if (outCount < count) {
1946 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1951 /* Remove all highlighting escape sequences in s
1952 Also deletes any suffix starting with '('
1955 StripHighlightAndTitle (char *s)
1957 static char retbuf[MSG_SIZ];
1960 while (*s != NULLCHAR) {
1961 while (*s == '\033') {
1962 while (*s != NULLCHAR && !isalpha(*s)) s++;
1963 if (*s != NULLCHAR) s++;
1965 while (*s != NULLCHAR && *s != '\033') {
1966 if (*s == '(' || *s == '[') {
1977 /* Remove all highlighting escape sequences in s */
1979 StripHighlight (char *s)
1981 static char retbuf[MSG_SIZ];
1984 while (*s != NULLCHAR) {
1985 while (*s == '\033') {
1986 while (*s != NULLCHAR && !isalpha(*s)) s++;
1987 if (*s != NULLCHAR) s++;
1989 while (*s != NULLCHAR && *s != '\033') {
1997 char *variantNames[] = VARIANT_NAMES;
1999 VariantName (VariantClass v)
2001 return variantNames[v];
2005 /* Identify a variant from the strings the chess servers use or the
2006 PGN Variant tag names we use. */
2008 StringToVariant (char *e)
2012 VariantClass v = VariantNormal;
2013 int i, found = FALSE;
2019 /* [HGM] skip over optional board-size prefixes */
2020 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2021 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2022 while( *e++ != '_');
2025 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2029 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2030 if (StrCaseStr(e, variantNames[i])) {
2031 v = (VariantClass) i;
2038 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2039 || StrCaseStr(e, "wild/fr")
2040 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2041 v = VariantFischeRandom;
2042 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2043 (i = 1, p = StrCaseStr(e, "w"))) {
2045 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2052 case 0: /* FICS only, actually */
2054 /* Castling legal even if K starts on d-file */
2055 v = VariantWildCastle;
2060 /* Castling illegal even if K & R happen to start in
2061 normal positions. */
2062 v = VariantNoCastle;
2075 /* Castling legal iff K & R start in normal positions */
2081 /* Special wilds for position setup; unclear what to do here */
2082 v = VariantLoadable;
2085 /* Bizarre ICC game */
2086 v = VariantTwoKings;
2089 v = VariantKriegspiel;
2095 v = VariantFischeRandom;
2098 v = VariantCrazyhouse;
2101 v = VariantBughouse;
2107 /* Not quite the same as FICS suicide! */
2108 v = VariantGiveaway;
2114 v = VariantShatranj;
2117 /* Temporary names for future ICC types. The name *will* change in
2118 the next xboard/WinBoard release after ICC defines it. */
2156 v = VariantCapablanca;
2159 v = VariantKnightmate;
2165 v = VariantCylinder;
2171 v = VariantCapaRandom;
2174 v = VariantBerolina;
2186 /* Found "wild" or "w" in the string but no number;
2187 must assume it's normal chess. */
2191 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2192 if( (len >= MSG_SIZ) && appData.debugMode )
2193 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2195 DisplayError(buf, 0);
2201 if (appData.debugMode) {
2202 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2203 e, wnum, VariantName(v));
2208 static int leftover_start = 0, leftover_len = 0;
2209 char star_match[STAR_MATCH_N][MSG_SIZ];
2211 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2212 advance *index beyond it, and set leftover_start to the new value of
2213 *index; else return FALSE. If pattern contains the character '*', it
2214 matches any sequence of characters not containing '\r', '\n', or the
2215 character following the '*' (if any), and the matched sequence(s) are
2216 copied into star_match.
2219 looking_at ( char *buf, int *index, char *pattern)
2221 char *bufp = &buf[*index], *patternp = pattern;
2223 char *matchp = star_match[0];
2226 if (*patternp == NULLCHAR) {
2227 *index = leftover_start = bufp - buf;
2231 if (*bufp == NULLCHAR) return FALSE;
2232 if (*patternp == '*') {
2233 if (*bufp == *(patternp + 1)) {
2235 matchp = star_match[++star_count];
2239 } else if (*bufp == '\n' || *bufp == '\r') {
2241 if (*patternp == NULLCHAR)
2246 *matchp++ = *bufp++;
2250 if (*patternp != *bufp) return FALSE;
2257 SendToPlayer (char *data, int length)
2259 int error, outCount;
2260 outCount = OutputToProcess(NoProc, data, length, &error);
2261 if (outCount < length) {
2262 DisplayFatalError(_("Error writing to display"), error, 1);
2267 PackHolding (char packed[], char *holding)
2277 switch (runlength) {
2288 sprintf(q, "%d", runlength);
2300 /* Telnet protocol requests from the front end */
2302 TelnetRequest (unsigned char ddww, unsigned char option)
2304 unsigned char msg[3];
2305 int outCount, outError;
2307 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2309 if (appData.debugMode) {
2310 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2326 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2335 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2338 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2343 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2345 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2352 if (!appData.icsActive) return;
2353 TelnetRequest(TN_DO, TN_ECHO);
2359 if (!appData.icsActive) return;
2360 TelnetRequest(TN_DONT, TN_ECHO);
2364 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2366 /* put the holdings sent to us by the server on the board holdings area */
2367 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2371 if(gameInfo.holdingsWidth < 2) return;
2372 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2373 return; // prevent overwriting by pre-board holdings
2375 if( (int)lowestPiece >= BlackPawn ) {
2378 holdingsStartRow = BOARD_HEIGHT-1;
2381 holdingsColumn = BOARD_WIDTH-1;
2382 countsColumn = BOARD_WIDTH-2;
2383 holdingsStartRow = 0;
2387 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2388 board[i][holdingsColumn] = EmptySquare;
2389 board[i][countsColumn] = (ChessSquare) 0;
2391 while( (p=*holdings++) != NULLCHAR ) {
2392 piece = CharToPiece( ToUpper(p) );
2393 if(piece == EmptySquare) continue;
2394 /*j = (int) piece - (int) WhitePawn;*/
2395 j = PieceToNumber(piece);
2396 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2397 if(j < 0) continue; /* should not happen */
2398 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2399 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2400 board[holdingsStartRow+j*direction][countsColumn]++;
2406 VariantSwitch (Board board, VariantClass newVariant)
2408 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2409 static Board oldBoard;
2411 startedFromPositionFile = FALSE;
2412 if(gameInfo.variant == newVariant) return;
2414 /* [HGM] This routine is called each time an assignment is made to
2415 * gameInfo.variant during a game, to make sure the board sizes
2416 * are set to match the new variant. If that means adding or deleting
2417 * holdings, we shift the playing board accordingly
2418 * This kludge is needed because in ICS observe mode, we get boards
2419 * of an ongoing game without knowing the variant, and learn about the
2420 * latter only later. This can be because of the move list we requested,
2421 * in which case the game history is refilled from the beginning anyway,
2422 * but also when receiving holdings of a crazyhouse game. In the latter
2423 * case we want to add those holdings to the already received position.
2427 if (appData.debugMode) {
2428 fprintf(debugFP, "Switch board from %s to %s\n",
2429 VariantName(gameInfo.variant), VariantName(newVariant));
2430 setbuf(debugFP, NULL);
2432 shuffleOpenings = 0; /* [HGM] shuffle */
2433 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2437 newWidth = 9; newHeight = 9;
2438 gameInfo.holdingsSize = 7;
2439 case VariantBughouse:
2440 case VariantCrazyhouse:
2441 newHoldingsWidth = 2; break;
2445 newHoldingsWidth = 2;
2446 gameInfo.holdingsSize = 8;
2449 case VariantCapablanca:
2450 case VariantCapaRandom:
2453 newHoldingsWidth = gameInfo.holdingsSize = 0;
2456 if(newWidth != gameInfo.boardWidth ||
2457 newHeight != gameInfo.boardHeight ||
2458 newHoldingsWidth != gameInfo.holdingsWidth ) {
2460 /* shift position to new playing area, if needed */
2461 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2462 for(i=0; i<BOARD_HEIGHT; i++)
2463 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2464 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2466 for(i=0; i<newHeight; i++) {
2467 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2468 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2470 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2471 for(i=0; i<BOARD_HEIGHT; i++)
2472 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2473 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2476 board[HOLDINGS_SET] = 0;
2477 gameInfo.boardWidth = newWidth;
2478 gameInfo.boardHeight = newHeight;
2479 gameInfo.holdingsWidth = newHoldingsWidth;
2480 gameInfo.variant = newVariant;
2481 InitDrawingSizes(-2, 0);
2482 } else gameInfo.variant = newVariant;
2483 CopyBoard(oldBoard, board); // remember correctly formatted board
2484 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2485 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2488 static int loggedOn = FALSE;
2490 /*-- Game start info cache: --*/
2492 char gs_kind[MSG_SIZ];
2493 static char player1Name[128] = "";
2494 static char player2Name[128] = "";
2495 static char cont_seq[] = "\n\\ ";
2496 static int player1Rating = -1;
2497 static int player2Rating = -1;
2498 /*----------------------------*/
2500 ColorClass curColor = ColorNormal;
2501 int suppressKibitz = 0;
2504 Boolean soughtPending = FALSE;
2505 Boolean seekGraphUp;
2506 #define MAX_SEEK_ADS 200
2508 char *seekAdList[MAX_SEEK_ADS];
2509 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2510 float tcList[MAX_SEEK_ADS];
2511 char colorList[MAX_SEEK_ADS];
2512 int nrOfSeekAds = 0;
2513 int minRating = 1010, maxRating = 2800;
2514 int hMargin = 10, vMargin = 20, h, w;
2515 extern int squareSize, lineGap;
2520 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2521 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2522 if(r < minRating+100 && r >=0 ) r = minRating+100;
2523 if(r > maxRating) r = maxRating;
2524 if(tc < 1.f) tc = 1.f;
2525 if(tc > 95.f) tc = 95.f;
2526 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2527 y = ((double)r - minRating)/(maxRating - minRating)
2528 * (h-vMargin-squareSize/8-1) + vMargin;
2529 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2530 if(strstr(seekAdList[i], " u ")) color = 1;
2531 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2532 !strstr(seekAdList[i], "bullet") &&
2533 !strstr(seekAdList[i], "blitz") &&
2534 !strstr(seekAdList[i], "standard") ) color = 2;
2535 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2536 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2540 PlotSingleSeekAd (int i)
2546 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2548 char buf[MSG_SIZ], *ext = "";
2549 VariantClass v = StringToVariant(type);
2550 if(strstr(type, "wild")) {
2551 ext = type + 4; // append wild number
2552 if(v == VariantFischeRandom) type = "chess960"; else
2553 if(v == VariantLoadable) type = "setup"; else
2554 type = VariantName(v);
2556 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2557 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2558 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2559 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2560 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2561 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2562 seekNrList[nrOfSeekAds] = nr;
2563 zList[nrOfSeekAds] = 0;
2564 seekAdList[nrOfSeekAds++] = StrSave(buf);
2565 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2570 EraseSeekDot (int i)
2572 int x = xList[i], y = yList[i], d=squareSize/4, k;
2573 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2574 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2575 // now replot every dot that overlapped
2576 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2577 int xx = xList[k], yy = yList[k];
2578 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2579 DrawSeekDot(xx, yy, colorList[k]);
2584 RemoveSeekAd (int nr)
2587 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2589 if(seekAdList[i]) free(seekAdList[i]);
2590 seekAdList[i] = seekAdList[--nrOfSeekAds];
2591 seekNrList[i] = seekNrList[nrOfSeekAds];
2592 ratingList[i] = ratingList[nrOfSeekAds];
2593 colorList[i] = colorList[nrOfSeekAds];
2594 tcList[i] = tcList[nrOfSeekAds];
2595 xList[i] = xList[nrOfSeekAds];
2596 yList[i] = yList[nrOfSeekAds];
2597 zList[i] = zList[nrOfSeekAds];
2598 seekAdList[nrOfSeekAds] = NULL;
2604 MatchSoughtLine (char *line)
2606 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2607 int nr, base, inc, u=0; char dummy;
2609 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2610 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2612 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2613 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2614 // match: compact and save the line
2615 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2625 if(!seekGraphUp) return FALSE;
2626 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2627 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2629 DrawSeekBackground(0, 0, w, h);
2630 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2631 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2632 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2633 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2635 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2638 snprintf(buf, MSG_SIZ, "%d", i);
2639 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2642 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2643 for(i=1; i<100; i+=(i<10?1:5)) {
2644 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2645 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2646 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2648 snprintf(buf, MSG_SIZ, "%d", i);
2649 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2652 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2657 SeekGraphClick (ClickType click, int x, int y, int moving)
2659 static int lastDown = 0, displayed = 0, lastSecond;
2660 if(y < 0) return FALSE;
2661 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2662 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2663 if(!seekGraphUp) return FALSE;
2664 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2665 DrawPosition(TRUE, NULL);
2668 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2669 if(click == Release || moving) return FALSE;
2671 soughtPending = TRUE;
2672 SendToICS(ics_prefix);
2673 SendToICS("sought\n"); // should this be "sought all"?
2674 } else { // issue challenge based on clicked ad
2675 int dist = 10000; int i, closest = 0, second = 0;
2676 for(i=0; i<nrOfSeekAds; i++) {
2677 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2678 if(d < dist) { dist = d; closest = i; }
2679 second += (d - zList[i] < 120); // count in-range ads
2680 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2684 second = (second > 1);
2685 if(displayed != closest || second != lastSecond) {
2686 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2687 lastSecond = second; displayed = closest;
2689 if(click == Press) {
2690 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2693 } // on press 'hit', only show info
2694 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2695 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2696 SendToICS(ics_prefix);
2698 return TRUE; // let incoming board of started game pop down the graph
2699 } else if(click == Release) { // release 'miss' is ignored
2700 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2701 if(moving == 2) { // right up-click
2702 nrOfSeekAds = 0; // refresh graph
2703 soughtPending = TRUE;
2704 SendToICS(ics_prefix);
2705 SendToICS("sought\n"); // should this be "sought all"?
2708 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2709 // press miss or release hit 'pop down' seek graph
2710 seekGraphUp = FALSE;
2711 DrawPosition(TRUE, NULL);
2717 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2719 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2720 #define STARTED_NONE 0
2721 #define STARTED_MOVES 1
2722 #define STARTED_BOARD 2
2723 #define STARTED_OBSERVE 3
2724 #define STARTED_HOLDINGS 4
2725 #define STARTED_CHATTER 5
2726 #define STARTED_COMMENT 6
2727 #define STARTED_MOVES_NOHIDE 7
2729 static int started = STARTED_NONE;
2730 static char parse[20000];
2731 static int parse_pos = 0;
2732 static char buf[BUF_SIZE + 1];
2733 static int firstTime = TRUE, intfSet = FALSE;
2734 static ColorClass prevColor = ColorNormal;
2735 static int savingComment = FALSE;
2736 static int cmatch = 0; // continuation sequence match
2743 int backup; /* [DM] For zippy color lines */
2745 char talker[MSG_SIZ]; // [HGM] chat
2748 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2750 if (appData.debugMode) {
2752 fprintf(debugFP, "<ICS: ");
2753 show_bytes(debugFP, data, count);
2754 fprintf(debugFP, "\n");
2758 if (appData.debugMode) { int f = forwardMostMove;
2759 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2760 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2761 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2764 /* If last read ended with a partial line that we couldn't parse,
2765 prepend it to the new read and try again. */
2766 if (leftover_len > 0) {
2767 for (i=0; i<leftover_len; i++)
2768 buf[i] = buf[leftover_start + i];
2771 /* copy new characters into the buffer */
2772 bp = buf + leftover_len;
2773 buf_len=leftover_len;
2774 for (i=0; i<count; i++)
2777 if (data[i] == '\r')
2780 // join lines split by ICS?
2781 if (!appData.noJoin)
2784 Joining just consists of finding matches against the
2785 continuation sequence, and discarding that sequence
2786 if found instead of copying it. So, until a match
2787 fails, there's nothing to do since it might be the
2788 complete sequence, and thus, something we don't want
2791 if (data[i] == cont_seq[cmatch])
2794 if (cmatch == strlen(cont_seq))
2796 cmatch = 0; // complete match. just reset the counter
2799 it's possible for the ICS to not include the space
2800 at the end of the last word, making our [correct]
2801 join operation fuse two separate words. the server
2802 does this when the space occurs at the width setting.
2804 if (!buf_len || buf[buf_len-1] != ' ')
2815 match failed, so we have to copy what matched before
2816 falling through and copying this character. In reality,
2817 this will only ever be just the newline character, but
2818 it doesn't hurt to be precise.
2820 strncpy(bp, cont_seq, cmatch);
2832 buf[buf_len] = NULLCHAR;
2833 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2838 while (i < buf_len) {
2839 /* Deal with part of the TELNET option negotiation
2840 protocol. We refuse to do anything beyond the
2841 defaults, except that we allow the WILL ECHO option,
2842 which ICS uses to turn off password echoing when we are
2843 directly connected to it. We reject this option
2844 if localLineEditing mode is on (always on in xboard)
2845 and we are talking to port 23, which might be a real
2846 telnet server that will try to keep WILL ECHO on permanently.
2848 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2849 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2850 unsigned char option;
2852 switch ((unsigned char) buf[++i]) {
2854 if (appData.debugMode)
2855 fprintf(debugFP, "\n<WILL ");
2856 switch (option = (unsigned char) buf[++i]) {
2858 if (appData.debugMode)
2859 fprintf(debugFP, "ECHO ");
2860 /* Reply only if this is a change, according
2861 to the protocol rules. */
2862 if (remoteEchoOption) break;
2863 if (appData.localLineEditing &&
2864 atoi(appData.icsPort) == TN_PORT) {
2865 TelnetRequest(TN_DONT, TN_ECHO);
2868 TelnetRequest(TN_DO, TN_ECHO);
2869 remoteEchoOption = TRUE;
2873 if (appData.debugMode)
2874 fprintf(debugFP, "%d ", option);
2875 /* Whatever this is, we don't want it. */
2876 TelnetRequest(TN_DONT, option);
2881 if (appData.debugMode)
2882 fprintf(debugFP, "\n<WONT ");
2883 switch (option = (unsigned char) buf[++i]) {
2885 if (appData.debugMode)
2886 fprintf(debugFP, "ECHO ");
2887 /* Reply only if this is a change, according
2888 to the protocol rules. */
2889 if (!remoteEchoOption) break;
2891 TelnetRequest(TN_DONT, TN_ECHO);
2892 remoteEchoOption = FALSE;
2895 if (appData.debugMode)
2896 fprintf(debugFP, "%d ", (unsigned char) option);
2897 /* Whatever this is, it must already be turned
2898 off, because we never agree to turn on
2899 anything non-default, so according to the
2900 protocol rules, we don't reply. */
2905 if (appData.debugMode)
2906 fprintf(debugFP, "\n<DO ");
2907 switch (option = (unsigned char) buf[++i]) {
2909 /* Whatever this is, we refuse to do it. */
2910 if (appData.debugMode)
2911 fprintf(debugFP, "%d ", option);
2912 TelnetRequest(TN_WONT, option);
2917 if (appData.debugMode)
2918 fprintf(debugFP, "\n<DONT ");
2919 switch (option = (unsigned char) buf[++i]) {
2921 if (appData.debugMode)
2922 fprintf(debugFP, "%d ", option);
2923 /* Whatever this is, we are already not doing
2924 it, because we never agree to do anything
2925 non-default, so according to the protocol
2926 rules, we don't reply. */
2931 if (appData.debugMode)
2932 fprintf(debugFP, "\n<IAC ");
2933 /* Doubled IAC; pass it through */
2937 if (appData.debugMode)
2938 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2939 /* Drop all other telnet commands on the floor */
2942 if (oldi > next_out)
2943 SendToPlayer(&buf[next_out], oldi - next_out);
2949 /* OK, this at least will *usually* work */
2950 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2954 if (loggedOn && !intfSet) {
2955 if (ics_type == ICS_ICC) {
2956 snprintf(str, MSG_SIZ,
2957 "/set-quietly interface %s\n/set-quietly style 12\n",
2959 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2960 strcat(str, "/set-2 51 1\n/set seek 1\n");
2961 } else if (ics_type == ICS_CHESSNET) {
2962 snprintf(str, MSG_SIZ, "/style 12\n");
2964 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2965 strcat(str, programVersion);
2966 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2967 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2968 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2970 strcat(str, "$iset nohighlight 1\n");
2972 strcat(str, "$iset lock 1\n$style 12\n");
2975 NotifyFrontendLogin();
2979 if (started == STARTED_COMMENT) {
2980 /* Accumulate characters in comment */
2981 parse[parse_pos++] = buf[i];
2982 if (buf[i] == '\n') {
2983 parse[parse_pos] = NULLCHAR;
2984 if(chattingPartner>=0) {
2986 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2987 OutputChatMessage(chattingPartner, mess);
2988 chattingPartner = -1;
2989 next_out = i+1; // [HGM] suppress printing in ICS window
2991 if(!suppressKibitz) // [HGM] kibitz
2992 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2993 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2994 int nrDigit = 0, nrAlph = 0, j;
2995 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2996 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2997 parse[parse_pos] = NULLCHAR;
2998 // try to be smart: if it does not look like search info, it should go to
2999 // ICS interaction window after all, not to engine-output window.
3000 for(j=0; j<parse_pos; j++) { // count letters and digits
3001 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3002 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3003 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3005 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3006 int depth=0; float score;
3007 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3008 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3009 pvInfoList[forwardMostMove-1].depth = depth;
3010 pvInfoList[forwardMostMove-1].score = 100*score;
3012 OutputKibitz(suppressKibitz, parse);
3015 if(gameMode == IcsObserving) // restore original ICS messages
3016 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3018 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3019 SendToPlayer(tmp, strlen(tmp));
3021 next_out = i+1; // [HGM] suppress printing in ICS window
3023 started = STARTED_NONE;
3025 /* Don't match patterns against characters in comment */
3030 if (started == STARTED_CHATTER) {
3031 if (buf[i] != '\n') {
3032 /* Don't match patterns against characters in chatter */
3036 started = STARTED_NONE;
3037 if(suppressKibitz) next_out = i+1;
3040 /* Kludge to deal with rcmd protocol */
3041 if (firstTime && looking_at(buf, &i, "\001*")) {
3042 DisplayFatalError(&buf[1], 0, 1);
3048 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3051 if (appData.debugMode)
3052 fprintf(debugFP, "ics_type %d\n", ics_type);
3055 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3056 ics_type = ICS_FICS;
3058 if (appData.debugMode)
3059 fprintf(debugFP, "ics_type %d\n", ics_type);
3062 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3063 ics_type = ICS_CHESSNET;
3065 if (appData.debugMode)
3066 fprintf(debugFP, "ics_type %d\n", ics_type);
3071 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3072 looking_at(buf, &i, "Logging you in as \"*\"") ||
3073 looking_at(buf, &i, "will be \"*\""))) {
3074 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3078 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3080 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3081 DisplayIcsInteractionTitle(buf);
3082 have_set_title = TRUE;
3085 /* skip finger notes */
3086 if (started == STARTED_NONE &&
3087 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3088 (buf[i] == '1' && buf[i+1] == '0')) &&
3089 buf[i+2] == ':' && buf[i+3] == ' ') {
3090 started = STARTED_CHATTER;
3096 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3097 if(appData.seekGraph) {
3098 if(soughtPending && MatchSoughtLine(buf+i)) {
3099 i = strstr(buf+i, "rated") - buf;
3100 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101 next_out = leftover_start = i;
3102 started = STARTED_CHATTER;
3103 suppressKibitz = TRUE;
3106 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3107 && looking_at(buf, &i, "* ads displayed")) {
3108 soughtPending = FALSE;
3113 if(appData.autoRefresh) {
3114 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3115 int s = (ics_type == ICS_ICC); // ICC format differs
3117 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3118 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3119 looking_at(buf, &i, "*% "); // eat prompt
3120 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3121 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122 next_out = i; // suppress
3125 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3126 char *p = star_match[0];
3128 if(seekGraphUp) RemoveSeekAd(atoi(p));
3129 while(*p && *p++ != ' '); // next
3131 looking_at(buf, &i, "*% "); // eat prompt
3132 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3139 /* skip formula vars */
3140 if (started == STARTED_NONE &&
3141 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3142 started = STARTED_CHATTER;
3147 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3148 if (appData.autoKibitz && started == STARTED_NONE &&
3149 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3150 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3151 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3152 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3153 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3154 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3155 suppressKibitz = TRUE;
3156 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3158 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3159 && (gameMode == IcsPlayingWhite)) ||
3160 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3161 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3162 started = STARTED_CHATTER; // own kibitz we simply discard
3164 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3165 parse_pos = 0; parse[0] = NULLCHAR;
3166 savingComment = TRUE;
3167 suppressKibitz = gameMode != IcsObserving ? 2 :
3168 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3172 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3173 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3174 && atoi(star_match[0])) {
3175 // suppress the acknowledgements of our own autoKibitz
3177 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3178 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3179 SendToPlayer(star_match[0], strlen(star_match[0]));
3180 if(looking_at(buf, &i, "*% ")) // eat prompt
3181 suppressKibitz = FALSE;
3185 } // [HGM] kibitz: end of patch
3187 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3189 // [HGM] chat: intercept tells by users for which we have an open chat window
3191 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3192 looking_at(buf, &i, "* whispers:") ||
3193 looking_at(buf, &i, "* kibitzes:") ||
3194 looking_at(buf, &i, "* shouts:") ||
3195 looking_at(buf, &i, "* c-shouts:") ||
3196 looking_at(buf, &i, "--> * ") ||
3197 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3198 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3199 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3200 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3202 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3203 chattingPartner = -1;
3205 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3206 for(p=0; p<MAX_CHAT; p++) {
3207 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3208 talker[0] = '['; strcat(talker, "] ");
3209 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3210 chattingPartner = p; break;
3213 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3214 for(p=0; p<MAX_CHAT; p++) {
3215 if(!strcmp("kibitzes", chatPartner[p])) {
3216 talker[0] = '['; strcat(talker, "] ");
3217 chattingPartner = p; break;
3220 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3221 for(p=0; p<MAX_CHAT; p++) {
3222 if(!strcmp("whispers", chatPartner[p])) {
3223 talker[0] = '['; strcat(talker, "] ");
3224 chattingPartner = p; break;
3227 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3228 if(buf[i-8] == '-' && buf[i-3] == 't')
3229 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3230 if(!strcmp("c-shouts", chatPartner[p])) {
3231 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3232 chattingPartner = p; break;
3235 if(chattingPartner < 0)
3236 for(p=0; p<MAX_CHAT; p++) {
3237 if(!strcmp("shouts", chatPartner[p])) {
3238 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3239 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3240 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3241 chattingPartner = p; break;
3245 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3246 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3247 talker[0] = 0; Colorize(ColorTell, FALSE);
3248 chattingPartner = p; break;
3250 if(chattingPartner<0) i = oldi; else {
3251 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3252 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3253 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3254 started = STARTED_COMMENT;
3255 parse_pos = 0; parse[0] = NULLCHAR;
3256 savingComment = 3 + chattingPartner; // counts as TRUE
3257 suppressKibitz = TRUE;
3260 } // [HGM] chat: end of patch
3263 if (appData.zippyTalk || appData.zippyPlay) {
3264 /* [DM] Backup address for color zippy lines */
3266 if (loggedOn == TRUE)
3267 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3268 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3270 } // [DM] 'else { ' deleted
3272 /* Regular tells and says */
3273 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3274 looking_at(buf, &i, "* (your partner) tells you: ") ||
3275 looking_at(buf, &i, "* says: ") ||
3276 /* Don't color "message" or "messages" output */
3277 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3278 looking_at(buf, &i, "*. * at *:*: ") ||
3279 looking_at(buf, &i, "--* (*:*): ") ||
3280 /* Message notifications (same color as tells) */
3281 looking_at(buf, &i, "* has left a message ") ||
3282 looking_at(buf, &i, "* just sent you a message:\n") ||
3283 /* Whispers and kibitzes */
3284 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3285 looking_at(buf, &i, "* kibitzes: ") ||
3287 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3289 if (tkind == 1 && strchr(star_match[0], ':')) {
3290 /* Avoid "tells you:" spoofs in channels */
3293 if (star_match[0][0] == NULLCHAR ||
3294 strchr(star_match[0], ' ') ||
3295 (tkind == 3 && strchr(star_match[1], ' '))) {
3296 /* Reject bogus matches */
3299 if (appData.colorize) {
3300 if (oldi > next_out) {
3301 SendToPlayer(&buf[next_out], oldi - next_out);
3306 Colorize(ColorTell, FALSE);
3307 curColor = ColorTell;
3310 Colorize(ColorKibitz, FALSE);
3311 curColor = ColorKibitz;
3314 p = strrchr(star_match[1], '(');
3321 Colorize(ColorChannel1, FALSE);
3322 curColor = ColorChannel1;
3324 Colorize(ColorChannel, FALSE);
3325 curColor = ColorChannel;
3329 curColor = ColorNormal;
3333 if (started == STARTED_NONE && appData.autoComment &&
3334 (gameMode == IcsObserving ||
3335 gameMode == IcsPlayingWhite ||
3336 gameMode == IcsPlayingBlack)) {
3337 parse_pos = i - oldi;
3338 memcpy(parse, &buf[oldi], parse_pos);
3339 parse[parse_pos] = NULLCHAR;
3340 started = STARTED_COMMENT;
3341 savingComment = TRUE;
3343 started = STARTED_CHATTER;
3344 savingComment = FALSE;
3351 if (looking_at(buf, &i, "* s-shouts: ") ||
3352 looking_at(buf, &i, "* c-shouts: ")) {
3353 if (appData.colorize) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorSShout, FALSE);
3359 curColor = ColorSShout;
3362 started = STARTED_CHATTER;
3366 if (looking_at(buf, &i, "--->")) {
3371 if (looking_at(buf, &i, "* shouts: ") ||
3372 looking_at(buf, &i, "--> ")) {
3373 if (appData.colorize) {
3374 if (oldi > next_out) {
3375 SendToPlayer(&buf[next_out], oldi - next_out);
3378 Colorize(ColorShout, FALSE);
3379 curColor = ColorShout;
3382 started = STARTED_CHATTER;
3386 if (looking_at( buf, &i, "Challenge:")) {
3387 if (appData.colorize) {
3388 if (oldi > next_out) {
3389 SendToPlayer(&buf[next_out], oldi - next_out);
3392 Colorize(ColorChallenge, FALSE);
3393 curColor = ColorChallenge;
3399 if (looking_at(buf, &i, "* offers you") ||
3400 looking_at(buf, &i, "* offers to be") ||
3401 looking_at(buf, &i, "* would like to") ||
3402 looking_at(buf, &i, "* requests to") ||
3403 looking_at(buf, &i, "Your opponent offers") ||
3404 looking_at(buf, &i, "Your opponent requests")) {
3406 if (appData.colorize) {
3407 if (oldi > next_out) {
3408 SendToPlayer(&buf[next_out], oldi - next_out);
3411 Colorize(ColorRequest, FALSE);
3412 curColor = ColorRequest;
3417 if (looking_at(buf, &i, "* (*) see