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;
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 TidyProgramName(cps->program, cps->host, cps->tidy);
785 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786 cps->analysisSupport = 2; /* detect */
787 cps->analyzing = FALSE;
788 cps->initDone = FALSE;
791 /* New features added by Tord: */
792 cps->useFEN960 = FALSE;
793 cps->useOOCastle = TRUE;
794 /* End of new features added by Tord. */
795 cps->fenOverride = appData.fenOverride[n];
797 /* [HGM] time odds: set factor for each machine */
798 cps->timeOdds = appData.timeOdds[n];
800 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801 cps->accumulateTC = appData.accumulateTC[n];
802 cps->maxNrOfSessions = 1;
807 cps->supportsNPS = UNKNOWN;
808 cps->memSize = FALSE;
809 cps->maxCores = FALSE;
810 cps->egtFormats[0] = NULLCHAR;
813 cps->optionSettings = appData.engOptions[n];
815 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816 cps->isUCI = appData.isUCI[n]; /* [AS] */
817 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819 if (appData.protocolVersion[n] > PROTOVER
820 || appData.protocolVersion[n] < 1)
825 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826 appData.protocolVersion[n]);
827 if( (len >= MSG_SIZ) && appData.debugMode )
828 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830 DisplayFatalError(buf, 0, 2);
834 cps->protocolVersion = appData.protocolVersion[n];
837 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
838 ParseFeatures(appData.featureDefaults, cps);
841 ChessProgramState *savCps;
849 if(WaitForEngine(savCps, LoadEngine)) return;
850 CommonEngineInit(); // recalculate time odds
851 if(gameInfo.variant != StringToVariant(appData.variant)) {
852 // we changed variant when loading the engine; this forces us to reset
853 Reset(TRUE, savCps != &first);
854 oldMode = BeginningOfGame; // to prevent restoring old mode
856 InitChessProgram(savCps, FALSE);
857 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
858 DisplayMessage("", "");
859 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
863 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
867 ReplaceEngine (ChessProgramState *cps, int n)
869 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
871 if(oldMode != BeginningOfGame) EditGameEvent();
874 appData.noChessProgram = FALSE;
875 appData.clockMode = TRUE;
878 if(n) return; // only startup first engine immediately; second can wait
879 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
883 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
884 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
886 static char resetOptions[] =
887 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
888 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
889 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
890 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
893 FloatToFront(char **list, char *engineLine)
895 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
897 if(appData.recentEngines <= 0) return;
898 TidyProgramName(engineLine, "localhost", tidy+1);
899 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
900 strncpy(buf+1, *list, MSG_SIZ-50);
901 if(p = strstr(buf, tidy)) { // tidy name appears in list
902 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
903 while(*p++ = *++q); // squeeze out
905 strcat(tidy, buf+1); // put list behind tidy name
906 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
907 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
908 ASSIGN(*list, tidy+1);
911 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
914 Load (ChessProgramState *cps, int i)
916 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
917 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
918 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
919 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
920 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
921 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
922 appData.firstProtocolVersion = PROTOVER;
923 ParseArgsFromString(buf);
925 ReplaceEngine(cps, i);
926 FloatToFront(&appData.recentEngineList, engineLine);
930 while(q = strchr(p, SLASH)) p = q+1;
931 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
932 if(engineDir[0] != NULLCHAR) {
933 ASSIGN(appData.directory[i], engineDir); p = engineName;
934 } else if(p != engineName) { // derive directory from engine path, when not given
936 ASSIGN(appData.directory[i], engineName);
938 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
939 } else { ASSIGN(appData.directory[i], "."); }
941 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
942 snprintf(command, MSG_SIZ, "%s %s", p, params);
945 ASSIGN(appData.chessProgram[i], p);
946 appData.isUCI[i] = isUCI;
947 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
948 appData.hasOwnBookUCI[i] = hasBook;
949 if(!nickName[0]) useNick = FALSE;
950 if(useNick) ASSIGN(appData.pgnName[i], nickName);
954 q = firstChessProgramNames;
955 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
956 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
957 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
958 quote, p, quote, appData.directory[i],
959 useNick ? " -fn \"" : "",
960 useNick ? nickName : "",
962 v1 ? " -firstProtocolVersion 1" : "",
963 hasBook ? "" : " -fNoOwnBookUCI",
964 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
965 storeVariant ? " -variant " : "",
966 storeVariant ? VariantName(gameInfo.variant) : "");
967 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
968 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
969 if(insert != q) insert[-1] = NULLCHAR;
970 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
972 FloatToFront(&appData.recentEngineList, buf);
974 ReplaceEngine(cps, i);
980 int matched, min, sec;
982 * Parse timeControl resource
984 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
985 appData.movesPerSession)) {
987 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
988 DisplayFatalError(buf, 0, 2);
992 * Parse searchTime resource
994 if (*appData.searchTime != NULLCHAR) {
995 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
997 searchTime = min * 60;
998 } else if (matched == 2) {
999 searchTime = min * 60 + sec;
1002 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1003 DisplayFatalError(buf, 0, 2);
1012 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1013 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1015 GetTimeMark(&programStartTime);
1016 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1017 appData.seedBase = random() + (random()<<15);
1018 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1020 ClearProgramStats();
1021 programStats.ok_to_send = 1;
1022 programStats.seen_stat = 0;
1025 * Initialize game list
1031 * Internet chess server status
1033 if (appData.icsActive) {
1034 appData.matchMode = FALSE;
1035 appData.matchGames = 0;
1037 appData.noChessProgram = !appData.zippyPlay;
1039 appData.zippyPlay = FALSE;
1040 appData.zippyTalk = FALSE;
1041 appData.noChessProgram = TRUE;
1043 if (*appData.icsHelper != NULLCHAR) {
1044 appData.useTelnet = TRUE;
1045 appData.telnetProgram = appData.icsHelper;
1048 appData.zippyTalk = appData.zippyPlay = FALSE;
1051 /* [AS] Initialize pv info list [HGM] and game state */
1055 for( i=0; i<=framePtr; i++ ) {
1056 pvInfoList[i].depth = -1;
1057 boards[i][EP_STATUS] = EP_NONE;
1058 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1064 /* [AS] Adjudication threshold */
1065 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1067 InitEngine(&first, 0);
1068 InitEngine(&second, 1);
1071 pairing.which = "pairing"; // pairing engine
1072 pairing.pr = NoProc;
1074 pairing.program = appData.pairingEngine;
1075 pairing.host = "localhost";
1078 if (appData.icsActive) {
1079 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1080 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1081 appData.clockMode = FALSE;
1082 first.sendTime = second.sendTime = 0;
1086 /* Override some settings from environment variables, for backward
1087 compatibility. Unfortunately it's not feasible to have the env
1088 vars just set defaults, at least in xboard. Ugh.
1090 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1095 if (!appData.icsActive) {
1099 /* Check for variants that are supported only in ICS mode,
1100 or not at all. Some that are accepted here nevertheless
1101 have bugs; see comments below.
1103 VariantClass variant = StringToVariant(appData.variant);
1105 case VariantBughouse: /* need four players and two boards */
1106 case VariantKriegspiel: /* need to hide pieces and move details */
1107 /* case VariantFischeRandom: (Fabien: moved below) */
1108 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1109 if( (len >= MSG_SIZ) && appData.debugMode )
1110 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1112 DisplayFatalError(buf, 0, 2);
1115 case VariantUnknown:
1116 case VariantLoadable:
1126 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1127 if( (len >= MSG_SIZ) && appData.debugMode )
1128 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1130 DisplayFatalError(buf, 0, 2);
1133 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1134 case VariantFairy: /* [HGM] TestLegality definitely off! */
1135 case VariantGothic: /* [HGM] should work */
1136 case VariantCapablanca: /* [HGM] should work */
1137 case VariantCourier: /* [HGM] initial forced moves not implemented */
1138 case VariantShogi: /* [HGM] could still mate with pawn drop */
1139 case VariantKnightmate: /* [HGM] should work */
1140 case VariantCylinder: /* [HGM] untested */
1141 case VariantFalcon: /* [HGM] untested */
1142 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1143 offboard interposition not understood */
1144 case VariantNormal: /* definitely works! */
1145 case VariantWildCastle: /* pieces not automatically shuffled */
1146 case VariantNoCastle: /* pieces not automatically shuffled */
1147 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1148 case VariantLosers: /* should work except for win condition,
1149 and doesn't know captures are mandatory */
1150 case VariantSuicide: /* should work except for win condition,
1151 and doesn't know captures are mandatory */
1152 case VariantGiveaway: /* should work except for win condition,
1153 and doesn't know captures are mandatory */
1154 case VariantTwoKings: /* should work */
1155 case VariantAtomic: /* should work except for win condition */
1156 case Variant3Check: /* should work except for win condition */
1157 case VariantShatranj: /* should work except for all win conditions */
1158 case VariantMakruk: /* should work except for draw countdown */
1159 case VariantBerolina: /* might work if TestLegality is off */
1160 case VariantCapaRandom: /* should work */
1161 case VariantJanus: /* should work */
1162 case VariantSuper: /* experimental */
1163 case VariantGreat: /* experimental, requires legality testing to be off */
1164 case VariantSChess: /* S-Chess, should work */
1165 case VariantGrand: /* should work */
1166 case VariantSpartan: /* should work */
1174 NextIntegerFromString (char ** str, long * value)
1179 while( *s == ' ' || *s == '\t' ) {
1185 if( *s >= '0' && *s <= '9' ) {
1186 while( *s >= '0' && *s <= '9' ) {
1187 *value = *value * 10 + (*s - '0');
1200 NextTimeControlFromString (char ** str, long * value)
1203 int result = NextIntegerFromString( str, &temp );
1206 *value = temp * 60; /* Minutes */
1207 if( **str == ':' ) {
1209 result = NextIntegerFromString( str, &temp );
1210 *value += temp; /* Seconds */
1218 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1219 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1220 int result = -1, type = 0; long temp, temp2;
1222 if(**str != ':') return -1; // old params remain in force!
1224 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1225 if( NextIntegerFromString( str, &temp ) ) return -1;
1226 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1229 /* time only: incremental or sudden-death time control */
1230 if(**str == '+') { /* increment follows; read it */
1232 if(**str == '!') type = *(*str)++; // Bronstein TC
1233 if(result = NextIntegerFromString( str, &temp2)) return -1;
1234 *inc = temp2 * 1000;
1235 if(**str == '.') { // read fraction of increment
1236 char *start = ++(*str);
1237 if(result = NextIntegerFromString( str, &temp2)) return -1;
1239 while(start++ < *str) temp2 /= 10;
1243 *moves = 0; *tc = temp * 1000; *incType = type;
1247 (*str)++; /* classical time control */
1248 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1260 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1261 { /* [HGM] get time to add from the multi-session time-control string */
1262 int incType, moves=1; /* kludge to force reading of first session */
1263 long time, increment;
1266 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1268 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1269 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1270 if(movenr == -1) return time; /* last move before new session */
1271 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1272 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1273 if(!moves) return increment; /* current session is incremental */
1274 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1275 } while(movenr >= -1); /* try again for next session */
1277 return 0; // no new time quota on this move
1281 ParseTimeControl (char *tc, float ti, int mps)
1285 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1288 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1289 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1290 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1294 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1296 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1299 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1301 snprintf(buf, MSG_SIZ, ":%s", mytc);
1303 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1305 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1310 /* Parse second time control */
1313 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1321 timeControl_2 = tc2 * 1000;
1331 timeControl = tc1 * 1000;
1334 timeIncrement = ti * 1000; /* convert to ms */
1335 movesPerSession = 0;
1338 movesPerSession = mps;
1346 if (appData.debugMode) {
1347 fprintf(debugFP, "%s\n", programVersion);
1349 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1351 set_cont_sequence(appData.wrapContSeq);
1352 if (appData.matchGames > 0) {
1353 appData.matchMode = TRUE;
1354 } else if (appData.matchMode) {
1355 appData.matchGames = 1;
1357 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1358 appData.matchGames = appData.sameColorGames;
1359 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1360 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1361 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1364 if (appData.noChessProgram || first.protocolVersion == 1) {
1367 /* kludge: allow timeout for initial "feature" commands */
1369 DisplayMessage("", _("Starting chess program"));
1370 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1375 CalculateIndex (int index, int gameNr)
1376 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1378 if(index > 0) return index; // fixed nmber
1379 if(index == 0) return 1;
1380 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1381 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1386 LoadGameOrPosition (int gameNr)
1387 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1388 if (*appData.loadGameFile != NULLCHAR) {
1389 if (!LoadGameFromFile(appData.loadGameFile,
1390 CalculateIndex(appData.loadGameIndex, gameNr),
1391 appData.loadGameFile, FALSE)) {
1392 DisplayFatalError(_("Bad game file"), 0, 1);
1395 } else if (*appData.loadPositionFile != NULLCHAR) {
1396 if (!LoadPositionFromFile(appData.loadPositionFile,
1397 CalculateIndex(appData.loadPositionIndex, gameNr),
1398 appData.loadPositionFile)) {
1399 DisplayFatalError(_("Bad position file"), 0, 1);
1407 ReserveGame (int gameNr, char resChar)
1409 FILE *tf = fopen(appData.tourneyFile, "r+");
1410 char *p, *q, c, buf[MSG_SIZ];
1411 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1412 safeStrCpy(buf, lastMsg, MSG_SIZ);
1413 DisplayMessage(_("Pick new game"), "");
1414 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1415 ParseArgsFromFile(tf);
1416 p = q = appData.results;
1417 if(appData.debugMode) {
1418 char *r = appData.participants;
1419 fprintf(debugFP, "results = '%s'\n", p);
1420 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1421 fprintf(debugFP, "\n");
1423 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1425 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1426 safeStrCpy(q, p, strlen(p) + 2);
1427 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1428 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1429 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1430 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1433 fseek(tf, -(strlen(p)+4), SEEK_END);
1435 if(c != '"') // depending on DOS or Unix line endings we can be one off
1436 fseek(tf, -(strlen(p)+2), SEEK_END);
1437 else fseek(tf, -(strlen(p)+3), SEEK_END);
1438 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1439 DisplayMessage(buf, "");
1440 free(p); appData.results = q;
1441 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1442 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1443 int round = appData.defaultMatchGames * appData.tourneyType;
1444 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1445 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1446 UnloadEngine(&first); // next game belongs to other pairing;
1447 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1449 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1453 MatchEvent (int mode)
1454 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1456 if(matchMode) { // already in match mode: switch it off
1458 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1461 // if(gameMode != BeginningOfGame) {
1462 // DisplayError(_("You can only start a match from the initial position."), 0);
1466 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1467 /* Set up machine vs. machine match */
1469 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1470 if(appData.tourneyFile[0]) {
1472 if(nextGame > appData.matchGames) {
1474 if(strchr(appData.results, '*') == NULL) {
1476 appData.tourneyCycles++;
1477 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1479 NextTourneyGame(-1, &dummy);
1481 if(nextGame <= appData.matchGames) {
1482 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1484 ScheduleDelayedEvent(NextMatchGame, 10000);
1489 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1490 DisplayError(buf, 0);
1491 appData.tourneyFile[0] = 0;
1495 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1496 DisplayFatalError(_("Can't have a match with no chess programs"),
1501 matchGame = roundNr = 1;
1502 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1506 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1509 InitBackEnd3 P((void))
1511 GameMode initialMode;
1515 InitChessProgram(&first, startedFromSetupPosition);
1517 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1518 free(programVersion);
1519 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1520 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1521 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1524 if (appData.icsActive) {
1526 /* [DM] Make a console window if needed [HGM] merged ifs */
1532 if (*appData.icsCommPort != NULLCHAR)
1533 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1534 appData.icsCommPort);
1536 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1537 appData.icsHost, appData.icsPort);
1539 if( (len >= MSG_SIZ) && appData.debugMode )
1540 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1542 DisplayFatalError(buf, err, 1);
1547 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1549 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1550 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1551 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1552 } else if (appData.noChessProgram) {
1558 if (*appData.cmailGameName != NULLCHAR) {
1560 OpenLoopback(&cmailPR);
1562 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1566 DisplayMessage("", "");
1567 if (StrCaseCmp(appData.initialMode, "") == 0) {
1568 initialMode = BeginningOfGame;
1569 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1570 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1571 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1572 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1575 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1576 initialMode = TwoMachinesPlay;
1577 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1578 initialMode = AnalyzeFile;
1579 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1580 initialMode = AnalyzeMode;
1581 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1582 initialMode = MachinePlaysWhite;
1583 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1584 initialMode = MachinePlaysBlack;
1585 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1586 initialMode = EditGame;
1587 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1588 initialMode = EditPosition;
1589 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1590 initialMode = Training;
1592 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1593 if( (len >= MSG_SIZ) && appData.debugMode )
1594 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1596 DisplayFatalError(buf, 0, 2);
1600 if (appData.matchMode) {
1601 if(appData.tourneyFile[0]) { // start tourney from command line
1603 if(f = fopen(appData.tourneyFile, "r")) {
1604 ParseArgsFromFile(f); // make sure tourney parmeters re known
1606 appData.clockMode = TRUE;
1608 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1611 } else if (*appData.cmailGameName != NULLCHAR) {
1612 /* Set up cmail mode */
1613 ReloadCmailMsgEvent(TRUE);
1615 /* Set up other modes */
1616 if (initialMode == AnalyzeFile) {
1617 if (*appData.loadGameFile == NULLCHAR) {
1618 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1622 if (*appData.loadGameFile != NULLCHAR) {
1623 (void) LoadGameFromFile(appData.loadGameFile,
1624 appData.loadGameIndex,
1625 appData.loadGameFile, TRUE);
1626 } else if (*appData.loadPositionFile != NULLCHAR) {
1627 (void) LoadPositionFromFile(appData.loadPositionFile,
1628 appData.loadPositionIndex,
1629 appData.loadPositionFile);
1630 /* [HGM] try to make self-starting even after FEN load */
1631 /* to allow automatic setup of fairy variants with wtm */
1632 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1633 gameMode = BeginningOfGame;
1634 setboardSpoiledMachineBlack = 1;
1636 /* [HGM] loadPos: make that every new game uses the setup */
1637 /* from file as long as we do not switch variant */
1638 if(!blackPlaysFirst) {
1639 startedFromPositionFile = TRUE;
1640 CopyBoard(filePosition, boards[0]);
1643 if (initialMode == AnalyzeMode) {
1644 if (appData.noChessProgram) {
1645 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1648 if (appData.icsActive) {
1649 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1653 } else if (initialMode == AnalyzeFile) {
1654 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1655 ShowThinkingEvent();
1657 AnalysisPeriodicEvent(1);
1658 } else if (initialMode == MachinePlaysWhite) {
1659 if (appData.noChessProgram) {
1660 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1664 if (appData.icsActive) {
1665 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1669 MachineWhiteEvent();
1670 } else if (initialMode == MachinePlaysBlack) {
1671 if (appData.noChessProgram) {
1672 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1676 if (appData.icsActive) {
1677 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1681 MachineBlackEvent();
1682 } else if (initialMode == TwoMachinesPlay) {
1683 if (appData.noChessProgram) {
1684 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1688 if (appData.icsActive) {
1689 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1694 } else if (initialMode == EditGame) {
1696 } else if (initialMode == EditPosition) {
1697 EditPositionEvent();
1698 } else if (initialMode == Training) {
1699 if (*appData.loadGameFile == NULLCHAR) {
1700 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1709 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1711 DisplayBook(current+1);
1713 MoveHistorySet( movelist, first, last, current, pvInfoList );
1715 EvalGraphSet( first, last, current, pvInfoList );
1717 MakeEngineOutputTitle();
1721 * Establish will establish a contact to a remote host.port.
1722 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1723 * used to talk to the host.
1724 * Returns 0 if okay, error code if not.
1731 if (*appData.icsCommPort != NULLCHAR) {
1732 /* Talk to the host through a serial comm port */
1733 return OpenCommPort(appData.icsCommPort, &icsPR);
1735 } else if (*appData.gateway != NULLCHAR) {
1736 if (*appData.remoteShell == NULLCHAR) {
1737 /* Use the rcmd protocol to run telnet program on a gateway host */
1738 snprintf(buf, sizeof(buf), "%s %s %s",
1739 appData.telnetProgram, appData.icsHost, appData.icsPort);
1740 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1743 /* Use the rsh program to run telnet program on a gateway host */
1744 if (*appData.remoteUser == NULLCHAR) {
1745 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1746 appData.gateway, appData.telnetProgram,
1747 appData.icsHost, appData.icsPort);
1749 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1750 appData.remoteShell, appData.gateway,
1751 appData.remoteUser, appData.telnetProgram,
1752 appData.icsHost, appData.icsPort);
1754 return StartChildProcess(buf, "", &icsPR);
1757 } else if (appData.useTelnet) {
1758 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1761 /* TCP socket interface differs somewhat between
1762 Unix and NT; handle details in the front end.
1764 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1769 EscapeExpand (char *p, char *q)
1770 { // [HGM] initstring: routine to shape up string arguments
1771 while(*p++ = *q++) if(p[-1] == '\\')
1773 case 'n': p[-1] = '\n'; break;
1774 case 'r': p[-1] = '\r'; break;
1775 case 't': p[-1] = '\t'; break;
1776 case '\\': p[-1] = '\\'; break;
1777 case 0: *p = 0; return;
1778 default: p[-1] = q[-1]; break;
1783 show_bytes (FILE *fp, char *buf, int count)
1786 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1787 fprintf(fp, "\\%03o", *buf & 0xff);
1796 /* Returns an errno value */
1798 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1800 char buf[8192], *p, *q, *buflim;
1801 int left, newcount, outcount;
1803 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1804 *appData.gateway != NULLCHAR) {
1805 if (appData.debugMode) {
1806 fprintf(debugFP, ">ICS: ");
1807 show_bytes(debugFP, message, count);
1808 fprintf(debugFP, "\n");
1810 return OutputToProcess(pr, message, count, outError);
1813 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1820 if (appData.debugMode) {
1821 fprintf(debugFP, ">ICS: ");
1822 show_bytes(debugFP, buf, newcount);
1823 fprintf(debugFP, "\n");
1825 outcount = OutputToProcess(pr, buf, newcount, outError);
1826 if (outcount < newcount) return -1; /* to be sure */
1833 } else if (((unsigned char) *p) == TN_IAC) {
1834 *q++ = (char) TN_IAC;
1841 if (appData.debugMode) {
1842 fprintf(debugFP, ">ICS: ");
1843 show_bytes(debugFP, buf, newcount);
1844 fprintf(debugFP, "\n");
1846 outcount = OutputToProcess(pr, buf, newcount, outError);
1847 if (outcount < newcount) return -1; /* to be sure */
1852 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1854 int outError, outCount;
1855 static int gotEof = 0;
1858 /* Pass data read from player on to ICS */
1861 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1862 if (outCount < count) {
1863 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1865 if(have_sent_ICS_logon == 2) {
1866 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1867 fprintf(ini, "%s", message);
1868 have_sent_ICS_logon = 3;
1870 have_sent_ICS_logon = 1;
1871 } else if(have_sent_ICS_logon == 3) {
1872 fprintf(ini, "%s", message);
1874 have_sent_ICS_logon = 1;
1876 } else if (count < 0) {
1877 RemoveInputSource(isr);
1878 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1879 } else if (gotEof++ > 0) {
1880 RemoveInputSource(isr);
1881 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1887 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1888 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1889 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1890 SendToICS("date\n");
1891 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1894 /* added routine for printf style output to ics */
1896 ics_printf (char *format, ...)
1898 char buffer[MSG_SIZ];
1901 va_start(args, format);
1902 vsnprintf(buffer, sizeof(buffer), format, args);
1903 buffer[sizeof(buffer)-1] = '\0';
1911 int count, outCount, outError;
1913 if (icsPR == NoProc) return;
1916 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1917 if (outCount < count) {
1918 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1922 /* This is used for sending logon scripts to the ICS. Sending
1923 without a delay causes problems when using timestamp on ICC
1924 (at least on my machine). */
1926 SendToICSDelayed (char *s, long msdelay)
1928 int count, outCount, outError;
1930 if (icsPR == NoProc) return;
1933 if (appData.debugMode) {
1934 fprintf(debugFP, ">ICS: ");
1935 show_bytes(debugFP, s, count);
1936 fprintf(debugFP, "\n");
1938 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1940 if (outCount < count) {
1941 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1946 /* Remove all highlighting escape sequences in s
1947 Also deletes any suffix starting with '('
1950 StripHighlightAndTitle (char *s)
1952 static char retbuf[MSG_SIZ];
1955 while (*s != NULLCHAR) {
1956 while (*s == '\033') {
1957 while (*s != NULLCHAR && !isalpha(*s)) s++;
1958 if (*s != NULLCHAR) s++;
1960 while (*s != NULLCHAR && *s != '\033') {
1961 if (*s == '(' || *s == '[') {
1972 /* Remove all highlighting escape sequences in s */
1974 StripHighlight (char *s)
1976 static char retbuf[MSG_SIZ];
1979 while (*s != NULLCHAR) {
1980 while (*s == '\033') {
1981 while (*s != NULLCHAR && !isalpha(*s)) s++;
1982 if (*s != NULLCHAR) s++;
1984 while (*s != NULLCHAR && *s != '\033') {
1992 char *variantNames[] = VARIANT_NAMES;
1994 VariantName (VariantClass v)
1996 return variantNames[v];
2000 /* Identify a variant from the strings the chess servers use or the
2001 PGN Variant tag names we use. */
2003 StringToVariant (char *e)
2007 VariantClass v = VariantNormal;
2008 int i, found = FALSE;
2014 /* [HGM] skip over optional board-size prefixes */
2015 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2016 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2017 while( *e++ != '_');
2020 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2024 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2025 if (StrCaseStr(e, variantNames[i])) {
2026 v = (VariantClass) i;
2033 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2034 || StrCaseStr(e, "wild/fr")
2035 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2036 v = VariantFischeRandom;
2037 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2038 (i = 1, p = StrCaseStr(e, "w"))) {
2040 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2047 case 0: /* FICS only, actually */
2049 /* Castling legal even if K starts on d-file */
2050 v = VariantWildCastle;
2055 /* Castling illegal even if K & R happen to start in
2056 normal positions. */
2057 v = VariantNoCastle;
2070 /* Castling legal iff K & R start in normal positions */
2076 /* Special wilds for position setup; unclear what to do here */
2077 v = VariantLoadable;
2080 /* Bizarre ICC game */
2081 v = VariantTwoKings;
2084 v = VariantKriegspiel;
2090 v = VariantFischeRandom;
2093 v = VariantCrazyhouse;
2096 v = VariantBughouse;
2102 /* Not quite the same as FICS suicide! */
2103 v = VariantGiveaway;
2109 v = VariantShatranj;
2112 /* Temporary names for future ICC types. The name *will* change in
2113 the next xboard/WinBoard release after ICC defines it. */
2151 v = VariantCapablanca;
2154 v = VariantKnightmate;
2160 v = VariantCylinder;
2166 v = VariantCapaRandom;
2169 v = VariantBerolina;
2181 /* Found "wild" or "w" in the string but no number;
2182 must assume it's normal chess. */
2186 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2187 if( (len >= MSG_SIZ) && appData.debugMode )
2188 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2190 DisplayError(buf, 0);
2196 if (appData.debugMode) {
2197 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2198 e, wnum, VariantName(v));
2203 static int leftover_start = 0, leftover_len = 0;
2204 char star_match[STAR_MATCH_N][MSG_SIZ];
2206 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2207 advance *index beyond it, and set leftover_start to the new value of
2208 *index; else return FALSE. If pattern contains the character '*', it
2209 matches any sequence of characters not containing '\r', '\n', or the
2210 character following the '*' (if any), and the matched sequence(s) are
2211 copied into star_match.
2214 looking_at ( char *buf, int *index, char *pattern)
2216 char *bufp = &buf[*index], *patternp = pattern;
2218 char *matchp = star_match[0];
2221 if (*patternp == NULLCHAR) {
2222 *index = leftover_start = bufp - buf;
2226 if (*bufp == NULLCHAR) return FALSE;
2227 if (*patternp == '*') {
2228 if (*bufp == *(patternp + 1)) {
2230 matchp = star_match[++star_count];
2234 } else if (*bufp == '\n' || *bufp == '\r') {
2236 if (*patternp == NULLCHAR)
2241 *matchp++ = *bufp++;
2245 if (*patternp != *bufp) return FALSE;
2252 SendToPlayer (char *data, int length)
2254 int error, outCount;
2255 outCount = OutputToProcess(NoProc, data, length, &error);
2256 if (outCount < length) {
2257 DisplayFatalError(_("Error writing to display"), error, 1);
2262 PackHolding (char packed[], char *holding)
2272 switch (runlength) {
2283 sprintf(q, "%d", runlength);
2295 /* Telnet protocol requests from the front end */
2297 TelnetRequest (unsigned char ddww, unsigned char option)
2299 unsigned char msg[3];
2300 int outCount, outError;
2302 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2304 if (appData.debugMode) {
2305 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2321 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2330 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2333 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2338 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2340 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2347 if (!appData.icsActive) return;
2348 TelnetRequest(TN_DO, TN_ECHO);
2354 if (!appData.icsActive) return;
2355 TelnetRequest(TN_DONT, TN_ECHO);
2359 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2361 /* put the holdings sent to us by the server on the board holdings area */
2362 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2366 if(gameInfo.holdingsWidth < 2) return;
2367 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2368 return; // prevent overwriting by pre-board holdings
2370 if( (int)lowestPiece >= BlackPawn ) {
2373 holdingsStartRow = BOARD_HEIGHT-1;
2376 holdingsColumn = BOARD_WIDTH-1;
2377 countsColumn = BOARD_WIDTH-2;
2378 holdingsStartRow = 0;
2382 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2383 board[i][holdingsColumn] = EmptySquare;
2384 board[i][countsColumn] = (ChessSquare) 0;
2386 while( (p=*holdings++) != NULLCHAR ) {
2387 piece = CharToPiece( ToUpper(p) );
2388 if(piece == EmptySquare) continue;
2389 /*j = (int) piece - (int) WhitePawn;*/
2390 j = PieceToNumber(piece);
2391 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2392 if(j < 0) continue; /* should not happen */
2393 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2394 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2395 board[holdingsStartRow+j*direction][countsColumn]++;
2401 VariantSwitch (Board board, VariantClass newVariant)
2403 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2404 static Board oldBoard;
2406 startedFromPositionFile = FALSE;
2407 if(gameInfo.variant == newVariant) return;
2409 /* [HGM] This routine is called each time an assignment is made to
2410 * gameInfo.variant during a game, to make sure the board sizes
2411 * are set to match the new variant. If that means adding or deleting
2412 * holdings, we shift the playing board accordingly
2413 * This kludge is needed because in ICS observe mode, we get boards
2414 * of an ongoing game without knowing the variant, and learn about the
2415 * latter only later. This can be because of the move list we requested,
2416 * in which case the game history is refilled from the beginning anyway,
2417 * but also when receiving holdings of a crazyhouse game. In the latter
2418 * case we want to add those holdings to the already received position.
2422 if (appData.debugMode) {
2423 fprintf(debugFP, "Switch board from %s to %s\n",
2424 VariantName(gameInfo.variant), VariantName(newVariant));
2425 setbuf(debugFP, NULL);
2427 shuffleOpenings = 0; /* [HGM] shuffle */
2428 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2432 newWidth = 9; newHeight = 9;
2433 gameInfo.holdingsSize = 7;
2434 case VariantBughouse:
2435 case VariantCrazyhouse:
2436 newHoldingsWidth = 2; break;
2440 newHoldingsWidth = 2;
2441 gameInfo.holdingsSize = 8;
2444 case VariantCapablanca:
2445 case VariantCapaRandom:
2448 newHoldingsWidth = gameInfo.holdingsSize = 0;
2451 if(newWidth != gameInfo.boardWidth ||
2452 newHeight != gameInfo.boardHeight ||
2453 newHoldingsWidth != gameInfo.holdingsWidth ) {
2455 /* shift position to new playing area, if needed */
2456 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2457 for(i=0; i<BOARD_HEIGHT; i++)
2458 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2459 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2461 for(i=0; i<newHeight; i++) {
2462 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2463 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2465 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2466 for(i=0; i<BOARD_HEIGHT; i++)
2467 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2468 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2471 board[HOLDINGS_SET] = 0;
2472 gameInfo.boardWidth = newWidth;
2473 gameInfo.boardHeight = newHeight;
2474 gameInfo.holdingsWidth = newHoldingsWidth;
2475 gameInfo.variant = newVariant;
2476 InitDrawingSizes(-2, 0);
2477 } else gameInfo.variant = newVariant;
2478 CopyBoard(oldBoard, board); // remember correctly formatted board
2479 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2480 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2483 static int loggedOn = FALSE;
2485 /*-- Game start info cache: --*/
2487 char gs_kind[MSG_SIZ];
2488 static char player1Name[128] = "";
2489 static char player2Name[128] = "";
2490 static char cont_seq[] = "\n\\ ";
2491 static int player1Rating = -1;
2492 static int player2Rating = -1;
2493 /*----------------------------*/
2495 ColorClass curColor = ColorNormal;
2496 int suppressKibitz = 0;
2499 Boolean soughtPending = FALSE;
2500 Boolean seekGraphUp;
2501 #define MAX_SEEK_ADS 200
2503 char *seekAdList[MAX_SEEK_ADS];
2504 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2505 float tcList[MAX_SEEK_ADS];
2506 char colorList[MAX_SEEK_ADS];
2507 int nrOfSeekAds = 0;
2508 int minRating = 1010, maxRating = 2800;
2509 int hMargin = 10, vMargin = 20, h, w;
2510 extern int squareSize, lineGap;
2515 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2516 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2517 if(r < minRating+100 && r >=0 ) r = minRating+100;
2518 if(r > maxRating) r = maxRating;
2519 if(tc < 1.f) tc = 1.f;
2520 if(tc > 95.f) tc = 95.f;
2521 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2522 y = ((double)r - minRating)/(maxRating - minRating)
2523 * (h-vMargin-squareSize/8-1) + vMargin;
2524 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2525 if(strstr(seekAdList[i], " u ")) color = 1;
2526 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2527 !strstr(seekAdList[i], "bullet") &&
2528 !strstr(seekAdList[i], "blitz") &&
2529 !strstr(seekAdList[i], "standard") ) color = 2;
2530 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2531 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2535 PlotSingleSeekAd (int i)
2541 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2543 char buf[MSG_SIZ], *ext = "";
2544 VariantClass v = StringToVariant(type);
2545 if(strstr(type, "wild")) {
2546 ext = type + 4; // append wild number
2547 if(v == VariantFischeRandom) type = "chess960"; else
2548 if(v == VariantLoadable) type = "setup"; else
2549 type = VariantName(v);
2551 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2552 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2553 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2554 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2555 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2556 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2557 seekNrList[nrOfSeekAds] = nr;
2558 zList[nrOfSeekAds] = 0;
2559 seekAdList[nrOfSeekAds++] = StrSave(buf);
2560 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2565 EraseSeekDot (int i)
2567 int x = xList[i], y = yList[i], d=squareSize/4, k;
2568 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2569 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2570 // now replot every dot that overlapped
2571 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2572 int xx = xList[k], yy = yList[k];
2573 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2574 DrawSeekDot(xx, yy, colorList[k]);
2579 RemoveSeekAd (int nr)
2582 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2584 if(seekAdList[i]) free(seekAdList[i]);
2585 seekAdList[i] = seekAdList[--nrOfSeekAds];
2586 seekNrList[i] = seekNrList[nrOfSeekAds];
2587 ratingList[i] = ratingList[nrOfSeekAds];
2588 colorList[i] = colorList[nrOfSeekAds];
2589 tcList[i] = tcList[nrOfSeekAds];
2590 xList[i] = xList[nrOfSeekAds];
2591 yList[i] = yList[nrOfSeekAds];
2592 zList[i] = zList[nrOfSeekAds];
2593 seekAdList[nrOfSeekAds] = NULL;
2599 MatchSoughtLine (char *line)
2601 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2602 int nr, base, inc, u=0; char dummy;
2604 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2605 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2607 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2608 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2609 // match: compact and save the line
2610 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2620 if(!seekGraphUp) return FALSE;
2621 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2622 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2624 DrawSeekBackground(0, 0, w, h);
2625 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2626 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2627 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2628 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2630 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2633 snprintf(buf, MSG_SIZ, "%d", i);
2634 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2637 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2638 for(i=1; i<100; i+=(i<10?1:5)) {
2639 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2640 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2641 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2643 snprintf(buf, MSG_SIZ, "%d", i);
2644 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2647 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2652 SeekGraphClick (ClickType click, int x, int y, int moving)
2654 static int lastDown = 0, displayed = 0, lastSecond;
2655 if(y < 0) return FALSE;
2656 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2657 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2658 if(!seekGraphUp) return FALSE;
2659 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2660 DrawPosition(TRUE, NULL);
2663 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2664 if(click == Release || moving) return FALSE;
2666 soughtPending = TRUE;
2667 SendToICS(ics_prefix);
2668 SendToICS("sought\n"); // should this be "sought all"?
2669 } else { // issue challenge based on clicked ad
2670 int dist = 10000; int i, closest = 0, second = 0;
2671 for(i=0; i<nrOfSeekAds; i++) {
2672 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2673 if(d < dist) { dist = d; closest = i; }
2674 second += (d - zList[i] < 120); // count in-range ads
2675 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2679 second = (second > 1);
2680 if(displayed != closest || second != lastSecond) {
2681 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2682 lastSecond = second; displayed = closest;
2684 if(click == Press) {
2685 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2688 } // on press 'hit', only show info
2689 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2690 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2691 SendToICS(ics_prefix);
2693 return TRUE; // let incoming board of started game pop down the graph
2694 } else if(click == Release) { // release 'miss' is ignored
2695 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2696 if(moving == 2) { // right up-click
2697 nrOfSeekAds = 0; // refresh graph
2698 soughtPending = TRUE;
2699 SendToICS(ics_prefix);
2700 SendToICS("sought\n"); // should this be "sought all"?
2703 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2704 // press miss or release hit 'pop down' seek graph
2705 seekGraphUp = FALSE;
2706 DrawPosition(TRUE, NULL);
2712 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2714 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2715 #define STARTED_NONE 0
2716 #define STARTED_MOVES 1
2717 #define STARTED_BOARD 2
2718 #define STARTED_OBSERVE 3
2719 #define STARTED_HOLDINGS 4
2720 #define STARTED_CHATTER 5
2721 #define STARTED_COMMENT 6
2722 #define STARTED_MOVES_NOHIDE 7
2724 static int started = STARTED_NONE;
2725 static char parse[20000];
2726 static int parse_pos = 0;
2727 static char buf[BUF_SIZE + 1];
2728 static int firstTime = TRUE, intfSet = FALSE;
2729 static ColorClass prevColor = ColorNormal;
2730 static int savingComment = FALSE;
2731 static int cmatch = 0; // continuation sequence match
2738 int backup; /* [DM] For zippy color lines */
2740 char talker[MSG_SIZ]; // [HGM] chat
2743 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2745 if (appData.debugMode) {
2747 fprintf(debugFP, "<ICS: ");
2748 show_bytes(debugFP, data, count);
2749 fprintf(debugFP, "\n");
2753 if (appData.debugMode) { int f = forwardMostMove;
2754 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2755 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2756 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2759 /* If last read ended with a partial line that we couldn't parse,
2760 prepend it to the new read and try again. */
2761 if (leftover_len > 0) {
2762 for (i=0; i<leftover_len; i++)
2763 buf[i] = buf[leftover_start + i];
2766 /* copy new characters into the buffer */
2767 bp = buf + leftover_len;
2768 buf_len=leftover_len;
2769 for (i=0; i<count; i++)
2772 if (data[i] == '\r')
2775 // join lines split by ICS?
2776 if (!appData.noJoin)
2779 Joining just consists of finding matches against the
2780 continuation sequence, and discarding that sequence
2781 if found instead of copying it. So, until a match
2782 fails, there's nothing to do since it might be the
2783 complete sequence, and thus, something we don't want
2786 if (data[i] == cont_seq[cmatch])
2789 if (cmatch == strlen(cont_seq))
2791 cmatch = 0; // complete match. just reset the counter
2794 it's possible for the ICS to not include the space
2795 at the end of the last word, making our [correct]
2796 join operation fuse two separate words. the server
2797 does this when the space occurs at the width setting.
2799 if (!buf_len || buf[buf_len-1] != ' ')
2810 match failed, so we have to copy what matched before
2811 falling through and copying this character. In reality,
2812 this will only ever be just the newline character, but
2813 it doesn't hurt to be precise.
2815 strncpy(bp, cont_seq, cmatch);
2827 buf[buf_len] = NULLCHAR;
2828 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2833 while (i < buf_len) {
2834 /* Deal with part of the TELNET option negotiation
2835 protocol. We refuse to do anything beyond the
2836 defaults, except that we allow the WILL ECHO option,
2837 which ICS uses to turn off password echoing when we are
2838 directly connected to it. We reject this option
2839 if localLineEditing mode is on (always on in xboard)
2840 and we are talking to port 23, which might be a real
2841 telnet server that will try to keep WILL ECHO on permanently.
2843 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2844 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2845 unsigned char option;
2847 switch ((unsigned char) buf[++i]) {
2849 if (appData.debugMode)
2850 fprintf(debugFP, "\n<WILL ");
2851 switch (option = (unsigned char) buf[++i]) {
2853 if (appData.debugMode)
2854 fprintf(debugFP, "ECHO ");
2855 /* Reply only if this is a change, according
2856 to the protocol rules. */
2857 if (remoteEchoOption) break;
2858 if (appData.localLineEditing &&
2859 atoi(appData.icsPort) == TN_PORT) {
2860 TelnetRequest(TN_DONT, TN_ECHO);
2863 TelnetRequest(TN_DO, TN_ECHO);
2864 remoteEchoOption = TRUE;
2868 if (appData.debugMode)
2869 fprintf(debugFP, "%d ", option);
2870 /* Whatever this is, we don't want it. */
2871 TelnetRequest(TN_DONT, option);
2876 if (appData.debugMode)
2877 fprintf(debugFP, "\n<WONT ");
2878 switch (option = (unsigned char) buf[++i]) {
2880 if (appData.debugMode)
2881 fprintf(debugFP, "ECHO ");
2882 /* Reply only if this is a change, according
2883 to the protocol rules. */
2884 if (!remoteEchoOption) break;
2886 TelnetRequest(TN_DONT, TN_ECHO);
2887 remoteEchoOption = FALSE;
2890 if (appData.debugMode)
2891 fprintf(debugFP, "%d ", (unsigned char) option);
2892 /* Whatever this is, it must already be turned
2893 off, because we never agree to turn on
2894 anything non-default, so according to the
2895 protocol rules, we don't reply. */
2900 if (appData.debugMode)
2901 fprintf(debugFP, "\n<DO ");
2902 switch (option = (unsigned char) buf[++i]) {
2904 /* Whatever this is, we refuse to do it. */
2905 if (appData.debugMode)
2906 fprintf(debugFP, "%d ", option);
2907 TelnetRequest(TN_WONT, option);
2912 if (appData.debugMode)
2913 fprintf(debugFP, "\n<DONT ");
2914 switch (option = (unsigned char) buf[++i]) {
2916 if (appData.debugMode)
2917 fprintf(debugFP, "%d ", option);
2918 /* Whatever this is, we are already not doing
2919 it, because we never agree to do anything
2920 non-default, so according to the protocol
2921 rules, we don't reply. */
2926 if (appData.debugMode)
2927 fprintf(debugFP, "\n<IAC ");
2928 /* Doubled IAC; pass it through */
2932 if (appData.debugMode)
2933 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2934 /* Drop all other telnet commands on the floor */
2937 if (oldi > next_out)
2938 SendToPlayer(&buf[next_out], oldi - next_out);
2944 /* OK, this at least will *usually* work */
2945 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2949 if (loggedOn && !intfSet) {
2950 if (ics_type == ICS_ICC) {
2951 snprintf(str, MSG_SIZ,
2952 "/set-quietly interface %s\n/set-quietly style 12\n",
2954 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2955 strcat(str, "/set-2 51 1\n/set seek 1\n");
2956 } else if (ics_type == ICS_CHESSNET) {
2957 snprintf(str, MSG_SIZ, "/style 12\n");
2959 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2960 strcat(str, programVersion);
2961 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2962 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2963 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2965 strcat(str, "$iset nohighlight 1\n");
2967 strcat(str, "$iset lock 1\n$style 12\n");
2970 NotifyFrontendLogin();
2974 if (started == STARTED_COMMENT) {
2975 /* Accumulate characters in comment */
2976 parse[parse_pos++] = buf[i];
2977 if (buf[i] == '\n') {
2978 parse[parse_pos] = NULLCHAR;
2979 if(chattingPartner>=0) {
2981 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2982 OutputChatMessage(chattingPartner, mess);
2983 chattingPartner = -1;
2984 next_out = i+1; // [HGM] suppress printing in ICS window
2986 if(!suppressKibitz) // [HGM] kibitz
2987 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2988 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2989 int nrDigit = 0, nrAlph = 0, j;
2990 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2991 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2992 parse[parse_pos] = NULLCHAR;
2993 // try to be smart: if it does not look like search info, it should go to
2994 // ICS interaction window after all, not to engine-output window.
2995 for(j=0; j<parse_pos; j++) { // count letters and digits
2996 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2997 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2998 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3000 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3001 int depth=0; float score;
3002 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3003 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3004 pvInfoList[forwardMostMove-1].depth = depth;
3005 pvInfoList[forwardMostMove-1].score = 100*score;
3007 OutputKibitz(suppressKibitz, parse);
3010 if(gameMode == IcsObserving) // restore original ICS messages
3011 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3013 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3014 SendToPlayer(tmp, strlen(tmp));
3016 next_out = i+1; // [HGM] suppress printing in ICS window
3018 started = STARTED_NONE;
3020 /* Don't match patterns against characters in comment */
3025 if (started == STARTED_CHATTER) {
3026 if (buf[i] != '\n') {
3027 /* Don't match patterns against characters in chatter */
3031 started = STARTED_NONE;
3032 if(suppressKibitz) next_out = i+1;
3035 /* Kludge to deal with rcmd protocol */
3036 if (firstTime && looking_at(buf, &i, "\001*")) {
3037 DisplayFatalError(&buf[1], 0, 1);
3043 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3046 if (appData.debugMode)
3047 fprintf(debugFP, "ics_type %d\n", ics_type);
3050 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3051 ics_type = ICS_FICS;
3053 if (appData.debugMode)
3054 fprintf(debugFP, "ics_type %d\n", ics_type);
3057 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3058 ics_type = ICS_CHESSNET;
3060 if (appData.debugMode)
3061 fprintf(debugFP, "ics_type %d\n", ics_type);
3066 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3067 looking_at(buf, &i, "Logging you in as \"*\"") ||
3068 looking_at(buf, &i, "will be \"*\""))) {
3069 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3073 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3075 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3076 DisplayIcsInteractionTitle(buf);
3077 have_set_title = TRUE;
3080 /* skip finger notes */
3081 if (started == STARTED_NONE &&
3082 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3083 (buf[i] == '1' && buf[i+1] == '0')) &&
3084 buf[i+2] == ':' && buf[i+3] == ' ') {
3085 started = STARTED_CHATTER;
3091 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3092 if(appData.seekGraph) {
3093 if(soughtPending && MatchSoughtLine(buf+i)) {
3094 i = strstr(buf+i, "rated") - buf;
3095 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096 next_out = leftover_start = i;
3097 started = STARTED_CHATTER;
3098 suppressKibitz = TRUE;
3101 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3102 && looking_at(buf, &i, "* ads displayed")) {
3103 soughtPending = FALSE;
3108 if(appData.autoRefresh) {
3109 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3110 int s = (ics_type == ICS_ICC); // ICC format differs
3112 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3113 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3114 looking_at(buf, &i, "*% "); // eat prompt
3115 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3116 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117 next_out = i; // suppress
3120 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3121 char *p = star_match[0];
3123 if(seekGraphUp) RemoveSeekAd(atoi(p));
3124 while(*p && *p++ != ' '); // next
3126 looking_at(buf, &i, "*% "); // eat prompt
3127 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3134 /* skip formula vars */
3135 if (started == STARTED_NONE &&
3136 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3137 started = STARTED_CHATTER;
3142 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3143 if (appData.autoKibitz && started == STARTED_NONE &&
3144 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3145 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3146 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3147 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3148 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3149 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3150 suppressKibitz = TRUE;
3151 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3154 && (gameMode == IcsPlayingWhite)) ||
3155 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3156 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3157 started = STARTED_CHATTER; // own kibitz we simply discard
3159 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3160 parse_pos = 0; parse[0] = NULLCHAR;
3161 savingComment = TRUE;
3162 suppressKibitz = gameMode != IcsObserving ? 2 :
3163 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3167 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3168 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3169 && atoi(star_match[0])) {
3170 // suppress the acknowledgements of our own autoKibitz
3172 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3173 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3174 SendToPlayer(star_match[0], strlen(star_match[0]));
3175 if(looking_at(buf, &i, "*% ")) // eat prompt
3176 suppressKibitz = FALSE;
3180 } // [HGM] kibitz: end of patch
3182 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3184 // [HGM] chat: intercept tells by users for which we have an open chat window
3186 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3187 looking_at(buf, &i, "* whispers:") ||
3188 looking_at(buf, &i, "* kibitzes:") ||
3189 looking_at(buf, &i, "* shouts:") ||
3190 looking_at(buf, &i, "* c-shouts:") ||
3191 looking_at(buf, &i, "--> * ") ||
3192 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3193 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3194 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3195 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3197 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3198 chattingPartner = -1;
3200 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3201 for(p=0; p<MAX_CHAT; p++) {
3202 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3203 talker[0] = '['; strcat(talker, "] ");
3204 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3205 chattingPartner = p; break;
3208 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3209 for(p=0; p<MAX_CHAT; p++) {
3210 if(!strcmp("kibitzes", chatPartner[p])) {
3211 talker[0] = '['; strcat(talker, "] ");
3212 chattingPartner = p; break;
3215 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3216 for(p=0; p<MAX_CHAT; p++) {
3217 if(!strcmp("whispers", chatPartner[p])) {
3218 talker[0] = '['; strcat(talker, "] ");
3219 chattingPartner = p; break;
3222 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3223 if(buf[i-8] == '-' && buf[i-3] == 't')
3224 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3225 if(!strcmp("c-shouts", chatPartner[p])) {
3226 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3227 chattingPartner = p; break;
3230 if(chattingPartner < 0)
3231 for(p=0; p<MAX_CHAT; p++) {
3232 if(!strcmp("shouts", chatPartner[p])) {
3233 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3234 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3235 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3236 chattingPartner = p; break;
3240 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3241 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3242 talker[0] = 0; Colorize(ColorTell, FALSE);
3243 chattingPartner = p; break;
3245 if(chattingPartner<0) i = oldi; else {
3246 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3247 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3248 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3249 started = STARTED_COMMENT;
3250 parse_pos = 0; parse[0] = NULLCHAR;
3251 savingComment = 3 + chattingPartner; // counts as TRUE
3252 suppressKibitz = TRUE;
3255 } // [HGM] chat: end of patch
3258 if (appData.zippyTalk || appData.zippyPlay) {
3259 /* [DM] Backup address for color zippy lines */
3261 if (loggedOn == TRUE)
3262 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3263 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3265 } // [DM] 'else { ' deleted
3267 /* Regular tells and says */
3268 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3269 looking_at(buf, &i, "* (your partner) tells you: ") ||
3270 looking_at(buf, &i, "* says: ") ||
3271 /* Don't color "message" or "messages" output */
3272 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3273 looking_at(buf, &i, "*. * at *:*: ") ||
3274 looking_at(buf, &i, "--* (*:*): ") ||
3275 /* Message notifications (same color as tells) */
3276 looking_at(buf, &i, "* has left a message ") ||
3277 looking_at(buf, &i, "* just sent you a message:\n") ||
3278 /* Whispers and kibitzes */
3279 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3280 looking_at(buf, &i, "* kibitzes: ") ||
3282 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3284 if (tkind == 1 && strchr(star_match[0], ':')) {
3285 /* Avoid "tells you:" spoofs in channels */
3288 if (star_match[0][0] == NULLCHAR ||
3289 strchr(star_match[0], ' ') ||
3290 (tkind == 3 && strchr(star_match[1], ' '))) {
3291 /* Reject bogus matches */
3294 if (appData.colorize) {
3295 if (oldi > next_out) {
3296 SendToPlayer(&buf[next_out], oldi - next_out);
3301 Colorize(ColorTell, FALSE);
3302 curColor = ColorTell;
3305 Colorize(ColorKibitz, FALSE);
3306 curColor = ColorKibitz;
3309 p = strrchr(star_match[1], '(');
3316 Colorize(ColorChannel1, FALSE);
3317 curColor = ColorChannel1;
3319 Colorize(ColorChannel, FALSE);
3320 curColor = ColorChannel;
3324 curColor = ColorNormal;
3328 if (started == STARTED_NONE && appData.autoComment &&
3329 (gameMode == IcsObserving ||
3330 gameMode == IcsPlayingWhite ||
3331 gameMode == IcsPlayingBlack)) {
3332 parse_pos = i - oldi;
3333 memcpy(parse, &buf[oldi], parse_pos);
3334 parse[parse_pos] = NULLCHAR;
3335 started = STARTED_COMMENT;
3336 savingComment = TRUE;
3338 started = STARTED_CHATTER;
3339 savingComment = FALSE;
3346 if (looking_at(buf, &i, "* s-shouts: ") ||
3347 looking_at(buf, &i, "* c-shouts: ")) {
3348 if (appData.colorize) {
3349 if (oldi > next_out) {
3350 SendToPlayer(&buf[next_out], oldi - next_out);
3353 Colorize(ColorSShout, FALSE);
3354 curColor = ColorSShout;
3357 started = STARTED_CHATTER;
3361 if (looking_at(buf, &i, "--->")) {
3366 if (looking_at(buf, &i, "* shouts: ") ||
3367 looking_at(buf, &i, "--> ")) {
3368 if (appData.colorize) {
3369 if (oldi > next_out) {
3370 SendToPlayer(&buf[next_out], oldi - next_out);
3373 Colorize(ColorShout, FALSE);
3374 curColor = ColorShout;
3377 started = STARTED_CHATTER;
3381 if (looking_at( buf, &i, "Challenge:")) {
3382 if (appData.colorize) {
3383 if (oldi > next_out) {
3384 SendToPlayer(&buf[next_out], oldi - next_out);
3387 Colorize(ColorChallenge, FALSE);
3388 curColor = ColorChallenge;
3394 if (looking_at(buf, &i, "* offers you") ||
3395 looking_at(buf, &i, "* offers to be") ||
3396 looking_at(buf, &i, "* would like to") ||
3397 looking_at(buf, &i, "* requests to") ||
3398 looking_at(buf, &i, "Your opponent offers") ||
3399 looking_at(buf, &i, "Your opponent requests")) {
3401 if (appData.colorize) {
3402 if (oldi > next_out) {
3403 SendToPlayer(&buf[next_out], oldi - next_out);
3406 Colorize(ColorRequest, FALSE);
3407 curColor = ColorRequest;
3412 if (looking_at(buf, &i, "* (*) seeking")) {
3413 if (appData.colorize) {
3414 if (oldi > next_out) {
3415 SendToPlayer(&buf[next_out], oldi - next_out);