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;
847 if(WaitForEngine(savCps, LoadEngine)) return;
848 CommonEngineInit(); // recalculate time odds
849 if(gameInfo.variant != StringToVariant(appData.variant)) {
850 // we changed variant when loading the engine; this forces us to reset
851 Reset(TRUE, savCps != &first);
852 EditGameEvent(); // for consistency with other path, as Reset changes mode
854 InitChessProgram(savCps, FALSE);
855 SendToProgram("force\n", savCps);
856 DisplayMessage("", "");
857 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
864 ReplaceEngine (ChessProgramState *cps, int n)
868 appData.noChessProgram = FALSE;
869 appData.clockMode = TRUE;
872 if(n) return; // only startup first engine immediately; second can wait
873 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
880 static char resetOptions[] =
881 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
884 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
887 FloatToFront(char **list, char *engineLine)
889 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
891 if(appData.recentEngines <= 0) return;
892 TidyProgramName(engineLine, "localhost", tidy+1);
893 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
894 strncpy(buf+1, *list, MSG_SIZ-50);
895 if(p = strstr(buf, tidy)) { // tidy name appears in list
896 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
897 while(*p++ = *++q); // squeeze out
899 strcat(tidy, buf+1); // put list behind tidy name
900 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
901 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
902 ASSIGN(*list, tidy+1);
905 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
908 Load (ChessProgramState *cps, int i)
910 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
911 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
912 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
913 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
914 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
915 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
916 appData.firstProtocolVersion = PROTOVER;
917 ParseArgsFromString(buf);
919 ReplaceEngine(cps, i);
920 FloatToFront(&appData.recentEngineList, engineLine);
924 while(q = strchr(p, SLASH)) p = q+1;
925 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
926 if(engineDir[0] != NULLCHAR) {
927 ASSIGN(appData.directory[i], engineDir); p = engineName;
928 } else if(p != engineName) { // derive directory from engine path, when not given
930 ASSIGN(appData.directory[i], engineName);
932 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
933 } else { ASSIGN(appData.directory[i], "."); }
935 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
936 snprintf(command, MSG_SIZ, "%s %s", p, params);
939 ASSIGN(appData.chessProgram[i], p);
940 appData.isUCI[i] = isUCI;
941 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
942 appData.hasOwnBookUCI[i] = hasBook;
943 if(!nickName[0]) useNick = FALSE;
944 if(useNick) ASSIGN(appData.pgnName[i], nickName);
948 q = firstChessProgramNames;
949 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
950 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
951 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
952 quote, p, quote, appData.directory[i],
953 useNick ? " -fn \"" : "",
954 useNick ? nickName : "",
956 v1 ? " -firstProtocolVersion 1" : "",
957 hasBook ? "" : " -fNoOwnBookUCI",
958 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
959 storeVariant ? " -variant " : "",
960 storeVariant ? VariantName(gameInfo.variant) : "");
961 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
962 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
963 if(insert != q) insert[-1] = NULLCHAR;
964 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
966 FloatToFront(&appData.recentEngineList, buf);
968 ReplaceEngine(cps, i);
974 int matched, min, sec;
976 * Parse timeControl resource
978 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
979 appData.movesPerSession)) {
981 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
982 DisplayFatalError(buf, 0, 2);
986 * Parse searchTime resource
988 if (*appData.searchTime != NULLCHAR) {
989 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
991 searchTime = min * 60;
992 } else if (matched == 2) {
993 searchTime = min * 60 + sec;
996 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
997 DisplayFatalError(buf, 0, 2);
1006 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1007 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1009 GetTimeMark(&programStartTime);
1010 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1011 appData.seedBase = random() + (random()<<15);
1012 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1014 ClearProgramStats();
1015 programStats.ok_to_send = 1;
1016 programStats.seen_stat = 0;
1019 * Initialize game list
1025 * Internet chess server status
1027 if (appData.icsActive) {
1028 appData.matchMode = FALSE;
1029 appData.matchGames = 0;
1031 appData.noChessProgram = !appData.zippyPlay;
1033 appData.zippyPlay = FALSE;
1034 appData.zippyTalk = FALSE;
1035 appData.noChessProgram = TRUE;
1037 if (*appData.icsHelper != NULLCHAR) {
1038 appData.useTelnet = TRUE;
1039 appData.telnetProgram = appData.icsHelper;
1042 appData.zippyTalk = appData.zippyPlay = FALSE;
1045 /* [AS] Initialize pv info list [HGM] and game state */
1049 for( i=0; i<=framePtr; i++ ) {
1050 pvInfoList[i].depth = -1;
1051 boards[i][EP_STATUS] = EP_NONE;
1052 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1058 /* [AS] Adjudication threshold */
1059 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1061 InitEngine(&first, 0);
1062 InitEngine(&second, 1);
1065 pairing.which = "pairing"; // pairing engine
1066 pairing.pr = NoProc;
1068 pairing.program = appData.pairingEngine;
1069 pairing.host = "localhost";
1072 if (appData.icsActive) {
1073 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1074 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1075 appData.clockMode = FALSE;
1076 first.sendTime = second.sendTime = 0;
1080 /* Override some settings from environment variables, for backward
1081 compatibility. Unfortunately it's not feasible to have the env
1082 vars just set defaults, at least in xboard. Ugh.
1084 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1089 if (!appData.icsActive) {
1093 /* Check for variants that are supported only in ICS mode,
1094 or not at all. Some that are accepted here nevertheless
1095 have bugs; see comments below.
1097 VariantClass variant = StringToVariant(appData.variant);
1099 case VariantBughouse: /* need four players and two boards */
1100 case VariantKriegspiel: /* need to hide pieces and move details */
1101 /* case VariantFischeRandom: (Fabien: moved below) */
1102 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1103 if( (len >= MSG_SIZ) && appData.debugMode )
1104 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1106 DisplayFatalError(buf, 0, 2);
1109 case VariantUnknown:
1110 case VariantLoadable:
1120 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1121 if( (len >= MSG_SIZ) && appData.debugMode )
1122 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1124 DisplayFatalError(buf, 0, 2);
1127 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1128 case VariantFairy: /* [HGM] TestLegality definitely off! */
1129 case VariantGothic: /* [HGM] should work */
1130 case VariantCapablanca: /* [HGM] should work */
1131 case VariantCourier: /* [HGM] initial forced moves not implemented */
1132 case VariantShogi: /* [HGM] could still mate with pawn drop */
1133 case VariantKnightmate: /* [HGM] should work */
1134 case VariantCylinder: /* [HGM] untested */
1135 case VariantFalcon: /* [HGM] untested */
1136 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1137 offboard interposition not understood */
1138 case VariantNormal: /* definitely works! */
1139 case VariantWildCastle: /* pieces not automatically shuffled */
1140 case VariantNoCastle: /* pieces not automatically shuffled */
1141 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1142 case VariantLosers: /* should work except for win condition,
1143 and doesn't know captures are mandatory */
1144 case VariantSuicide: /* should work except for win condition,
1145 and doesn't know captures are mandatory */
1146 case VariantGiveaway: /* should work except for win condition,
1147 and doesn't know captures are mandatory */
1148 case VariantTwoKings: /* should work */
1149 case VariantAtomic: /* should work except for win condition */
1150 case Variant3Check: /* should work except for win condition */
1151 case VariantShatranj: /* should work except for all win conditions */
1152 case VariantMakruk: /* should work except for draw countdown */
1153 case VariantBerolina: /* might work if TestLegality is off */
1154 case VariantCapaRandom: /* should work */
1155 case VariantJanus: /* should work */
1156 case VariantSuper: /* experimental */
1157 case VariantGreat: /* experimental, requires legality testing to be off */
1158 case VariantSChess: /* S-Chess, should work */
1159 case VariantGrand: /* should work */
1160 case VariantSpartan: /* should work */
1168 NextIntegerFromString (char ** str, long * value)
1173 while( *s == ' ' || *s == '\t' ) {
1179 if( *s >= '0' && *s <= '9' ) {
1180 while( *s >= '0' && *s <= '9' ) {
1181 *value = *value * 10 + (*s - '0');
1194 NextTimeControlFromString (char ** str, long * value)
1197 int result = NextIntegerFromString( str, &temp );
1200 *value = temp * 60; /* Minutes */
1201 if( **str == ':' ) {
1203 result = NextIntegerFromString( str, &temp );
1204 *value += temp; /* Seconds */
1212 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1213 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1214 int result = -1, type = 0; long temp, temp2;
1216 if(**str != ':') return -1; // old params remain in force!
1218 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1219 if( NextIntegerFromString( str, &temp ) ) return -1;
1220 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1223 /* time only: incremental or sudden-death time control */
1224 if(**str == '+') { /* increment follows; read it */
1226 if(**str == '!') type = *(*str)++; // Bronstein TC
1227 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228 *inc = temp2 * 1000;
1229 if(**str == '.') { // read fraction of increment
1230 char *start = ++(*str);
1231 if(result = NextIntegerFromString( str, &temp2)) return -1;
1233 while(start++ < *str) temp2 /= 10;
1237 *moves = 0; *tc = temp * 1000; *incType = type;
1241 (*str)++; /* classical time control */
1242 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1254 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1255 { /* [HGM] get time to add from the multi-session time-control string */
1256 int incType, moves=1; /* kludge to force reading of first session */
1257 long time, increment;
1260 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1262 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1263 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1264 if(movenr == -1) return time; /* last move before new session */
1265 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1266 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1267 if(!moves) return increment; /* current session is incremental */
1268 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1269 } while(movenr >= -1); /* try again for next session */
1271 return 0; // no new time quota on this move
1275 ParseTimeControl (char *tc, float ti, int mps)
1279 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1282 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1283 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1284 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1288 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1290 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1293 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1295 snprintf(buf, MSG_SIZ, ":%s", mytc);
1297 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1299 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1304 /* Parse second time control */
1307 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1315 timeControl_2 = tc2 * 1000;
1325 timeControl = tc1 * 1000;
1328 timeIncrement = ti * 1000; /* convert to ms */
1329 movesPerSession = 0;
1332 movesPerSession = mps;
1340 if (appData.debugMode) {
1341 fprintf(debugFP, "%s\n", programVersion);
1343 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1345 set_cont_sequence(appData.wrapContSeq);
1346 if (appData.matchGames > 0) {
1347 appData.matchMode = TRUE;
1348 } else if (appData.matchMode) {
1349 appData.matchGames = 1;
1351 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1352 appData.matchGames = appData.sameColorGames;
1353 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1354 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1355 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1358 if (appData.noChessProgram || first.protocolVersion == 1) {
1361 /* kludge: allow timeout for initial "feature" commands */
1363 DisplayMessage("", _("Starting chess program"));
1364 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1369 CalculateIndex (int index, int gameNr)
1370 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1372 if(index > 0) return index; // fixed nmber
1373 if(index == 0) return 1;
1374 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1375 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1380 LoadGameOrPosition (int gameNr)
1381 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1382 if (*appData.loadGameFile != NULLCHAR) {
1383 if (!LoadGameFromFile(appData.loadGameFile,
1384 CalculateIndex(appData.loadGameIndex, gameNr),
1385 appData.loadGameFile, FALSE)) {
1386 DisplayFatalError(_("Bad game file"), 0, 1);
1389 } else if (*appData.loadPositionFile != NULLCHAR) {
1390 if (!LoadPositionFromFile(appData.loadPositionFile,
1391 CalculateIndex(appData.loadPositionIndex, gameNr),
1392 appData.loadPositionFile)) {
1393 DisplayFatalError(_("Bad position file"), 0, 1);
1401 ReserveGame (int gameNr, char resChar)
1403 FILE *tf = fopen(appData.tourneyFile, "r+");
1404 char *p, *q, c, buf[MSG_SIZ];
1405 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1406 safeStrCpy(buf, lastMsg, MSG_SIZ);
1407 DisplayMessage(_("Pick new game"), "");
1408 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1409 ParseArgsFromFile(tf);
1410 p = q = appData.results;
1411 if(appData.debugMode) {
1412 char *r = appData.participants;
1413 fprintf(debugFP, "results = '%s'\n", p);
1414 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1415 fprintf(debugFP, "\n");
1417 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1419 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1420 safeStrCpy(q, p, strlen(p) + 2);
1421 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1422 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1423 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1424 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1427 fseek(tf, -(strlen(p)+4), SEEK_END);
1429 if(c != '"') // depending on DOS or Unix line endings we can be one off
1430 fseek(tf, -(strlen(p)+2), SEEK_END);
1431 else fseek(tf, -(strlen(p)+3), SEEK_END);
1432 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1433 DisplayMessage(buf, "");
1434 free(p); appData.results = q;
1435 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1436 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1437 int round = appData.defaultMatchGames * appData.tourneyType;
1438 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1439 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1440 UnloadEngine(&first); // next game belongs to other pairing;
1441 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1443 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1447 MatchEvent (int mode)
1448 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1450 if(matchMode) { // already in match mode: switch it off
1452 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1455 // if(gameMode != BeginningOfGame) {
1456 // DisplayError(_("You can only start a match from the initial position."), 0);
1460 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1461 /* Set up machine vs. machine match */
1463 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1464 if(appData.tourneyFile[0]) {
1466 if(nextGame > appData.matchGames) {
1468 if(strchr(appData.results, '*') == NULL) {
1470 appData.tourneyCycles++;
1471 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1473 NextTourneyGame(-1, &dummy);
1475 if(nextGame <= appData.matchGames) {
1476 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1478 ScheduleDelayedEvent(NextMatchGame, 10000);
1483 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1484 DisplayError(buf, 0);
1485 appData.tourneyFile[0] = 0;
1489 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1490 DisplayFatalError(_("Can't have a match with no chess programs"),
1495 matchGame = roundNr = 1;
1496 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1500 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1503 InitBackEnd3 P((void))
1505 GameMode initialMode;
1509 InitChessProgram(&first, startedFromSetupPosition);
1511 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1512 free(programVersion);
1513 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1514 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1515 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1518 if (appData.icsActive) {
1520 /* [DM] Make a console window if needed [HGM] merged ifs */
1526 if (*appData.icsCommPort != NULLCHAR)
1527 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1528 appData.icsCommPort);
1530 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1531 appData.icsHost, appData.icsPort);
1533 if( (len >= MSG_SIZ) && appData.debugMode )
1534 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1536 DisplayFatalError(buf, err, 1);
1541 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1543 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1544 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1545 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1546 } else if (appData.noChessProgram) {
1552 if (*appData.cmailGameName != NULLCHAR) {
1554 OpenLoopback(&cmailPR);
1556 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1560 DisplayMessage("", "");
1561 if (StrCaseCmp(appData.initialMode, "") == 0) {
1562 initialMode = BeginningOfGame;
1563 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1564 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1565 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1566 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1569 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1570 initialMode = TwoMachinesPlay;
1571 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1572 initialMode = AnalyzeFile;
1573 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1574 initialMode = AnalyzeMode;
1575 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1576 initialMode = MachinePlaysWhite;
1577 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1578 initialMode = MachinePlaysBlack;
1579 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1580 initialMode = EditGame;
1581 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1582 initialMode = EditPosition;
1583 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1584 initialMode = Training;
1586 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1587 if( (len >= MSG_SIZ) && appData.debugMode )
1588 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1590 DisplayFatalError(buf, 0, 2);
1594 if (appData.matchMode) {
1595 if(appData.tourneyFile[0]) { // start tourney from command line
1597 if(f = fopen(appData.tourneyFile, "r")) {
1598 ParseArgsFromFile(f); // make sure tourney parmeters re known
1600 appData.clockMode = TRUE;
1602 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1605 } else if (*appData.cmailGameName != NULLCHAR) {
1606 /* Set up cmail mode */
1607 ReloadCmailMsgEvent(TRUE);
1609 /* Set up other modes */
1610 if (initialMode == AnalyzeFile) {
1611 if (*appData.loadGameFile == NULLCHAR) {
1612 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1616 if (*appData.loadGameFile != NULLCHAR) {
1617 (void) LoadGameFromFile(appData.loadGameFile,
1618 appData.loadGameIndex,
1619 appData.loadGameFile, TRUE);
1620 } else if (*appData.loadPositionFile != NULLCHAR) {
1621 (void) LoadPositionFromFile(appData.loadPositionFile,
1622 appData.loadPositionIndex,
1623 appData.loadPositionFile);
1624 /* [HGM] try to make self-starting even after FEN load */
1625 /* to allow automatic setup of fairy variants with wtm */
1626 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1627 gameMode = BeginningOfGame;
1628 setboardSpoiledMachineBlack = 1;
1630 /* [HGM] loadPos: make that every new game uses the setup */
1631 /* from file as long as we do not switch variant */
1632 if(!blackPlaysFirst) {
1633 startedFromPositionFile = TRUE;
1634 CopyBoard(filePosition, boards[0]);
1637 if (initialMode == AnalyzeMode) {
1638 if (appData.noChessProgram) {
1639 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1642 if (appData.icsActive) {
1643 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1647 } else if (initialMode == AnalyzeFile) {
1648 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1649 ShowThinkingEvent();
1651 AnalysisPeriodicEvent(1);
1652 } else if (initialMode == MachinePlaysWhite) {
1653 if (appData.noChessProgram) {
1654 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1658 if (appData.icsActive) {
1659 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1663 MachineWhiteEvent();
1664 } else if (initialMode == MachinePlaysBlack) {
1665 if (appData.noChessProgram) {
1666 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1670 if (appData.icsActive) {
1671 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1675 MachineBlackEvent();
1676 } else if (initialMode == TwoMachinesPlay) {
1677 if (appData.noChessProgram) {
1678 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1682 if (appData.icsActive) {
1683 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1688 } else if (initialMode == EditGame) {
1690 } else if (initialMode == EditPosition) {
1691 EditPositionEvent();
1692 } else if (initialMode == Training) {
1693 if (*appData.loadGameFile == NULLCHAR) {
1694 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1703 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1705 DisplayBook(current+1);
1707 MoveHistorySet( movelist, first, last, current, pvInfoList );
1709 EvalGraphSet( first, last, current, pvInfoList );
1711 MakeEngineOutputTitle();
1715 * Establish will establish a contact to a remote host.port.
1716 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1717 * used to talk to the host.
1718 * Returns 0 if okay, error code if not.
1725 if (*appData.icsCommPort != NULLCHAR) {
1726 /* Talk to the host through a serial comm port */
1727 return OpenCommPort(appData.icsCommPort, &icsPR);
1729 } else if (*appData.gateway != NULLCHAR) {
1730 if (*appData.remoteShell == NULLCHAR) {
1731 /* Use the rcmd protocol to run telnet program on a gateway host */
1732 snprintf(buf, sizeof(buf), "%s %s %s",
1733 appData.telnetProgram, appData.icsHost, appData.icsPort);
1734 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1737 /* Use the rsh program to run telnet program on a gateway host */
1738 if (*appData.remoteUser == NULLCHAR) {
1739 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1740 appData.gateway, appData.telnetProgram,
1741 appData.icsHost, appData.icsPort);
1743 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1744 appData.remoteShell, appData.gateway,
1745 appData.remoteUser, appData.telnetProgram,
1746 appData.icsHost, appData.icsPort);
1748 return StartChildProcess(buf, "", &icsPR);
1751 } else if (appData.useTelnet) {
1752 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1755 /* TCP socket interface differs somewhat between
1756 Unix and NT; handle details in the front end.
1758 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1763 EscapeExpand (char *p, char *q)
1764 { // [HGM] initstring: routine to shape up string arguments
1765 while(*p++ = *q++) if(p[-1] == '\\')
1767 case 'n': p[-1] = '\n'; break;
1768 case 'r': p[-1] = '\r'; break;
1769 case 't': p[-1] = '\t'; break;
1770 case '\\': p[-1] = '\\'; break;
1771 case 0: *p = 0; return;
1772 default: p[-1] = q[-1]; break;
1777 show_bytes (FILE *fp, char *buf, int count)
1780 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1781 fprintf(fp, "\\%03o", *buf & 0xff);
1790 /* Returns an errno value */
1792 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1794 char buf[8192], *p, *q, *buflim;
1795 int left, newcount, outcount;
1797 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1798 *appData.gateway != NULLCHAR) {
1799 if (appData.debugMode) {
1800 fprintf(debugFP, ">ICS: ");
1801 show_bytes(debugFP, message, count);
1802 fprintf(debugFP, "\n");
1804 return OutputToProcess(pr, message, count, outError);
1807 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1814 if (appData.debugMode) {
1815 fprintf(debugFP, ">ICS: ");
1816 show_bytes(debugFP, buf, newcount);
1817 fprintf(debugFP, "\n");
1819 outcount = OutputToProcess(pr, buf, newcount, outError);
1820 if (outcount < newcount) return -1; /* to be sure */
1827 } else if (((unsigned char) *p) == TN_IAC) {
1828 *q++ = (char) TN_IAC;
1835 if (appData.debugMode) {
1836 fprintf(debugFP, ">ICS: ");
1837 show_bytes(debugFP, buf, newcount);
1838 fprintf(debugFP, "\n");
1840 outcount = OutputToProcess(pr, buf, newcount, outError);
1841 if (outcount < newcount) return -1; /* to be sure */
1846 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1848 int outError, outCount;
1849 static int gotEof = 0;
1852 /* Pass data read from player on to ICS */
1855 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1856 if (outCount < count) {
1857 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1859 if(have_sent_ICS_logon == 2) {
1860 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1861 fprintf(ini, "%s", message);
1862 have_sent_ICS_logon = 3;
1864 have_sent_ICS_logon = 1;
1865 } else if(have_sent_ICS_logon == 3) {
1866 fprintf(ini, "%s", message);
1868 have_sent_ICS_logon = 1;
1870 } else if (count < 0) {
1871 RemoveInputSource(isr);
1872 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1873 } else if (gotEof++ > 0) {
1874 RemoveInputSource(isr);
1875 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1881 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1882 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1883 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1884 SendToICS("date\n");
1885 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1888 /* added routine for printf style output to ics */
1890 ics_printf (char *format, ...)
1892 char buffer[MSG_SIZ];
1895 va_start(args, format);
1896 vsnprintf(buffer, sizeof(buffer), format, args);
1897 buffer[sizeof(buffer)-1] = '\0';
1905 int count, outCount, outError;
1907 if (icsPR == NoProc) return;
1910 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1911 if (outCount < count) {
1912 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916 /* This is used for sending logon scripts to the ICS. Sending
1917 without a delay causes problems when using timestamp on ICC
1918 (at least on my machine). */
1920 SendToICSDelayed (char *s, long msdelay)
1922 int count, outCount, outError;
1924 if (icsPR == NoProc) return;
1927 if (appData.debugMode) {
1928 fprintf(debugFP, ">ICS: ");
1929 show_bytes(debugFP, s, count);
1930 fprintf(debugFP, "\n");
1932 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1934 if (outCount < count) {
1935 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1940 /* Remove all highlighting escape sequences in s
1941 Also deletes any suffix starting with '('
1944 StripHighlightAndTitle (char *s)
1946 static char retbuf[MSG_SIZ];
1949 while (*s != NULLCHAR) {
1950 while (*s == '\033') {
1951 while (*s != NULLCHAR && !isalpha(*s)) s++;
1952 if (*s != NULLCHAR) s++;
1954 while (*s != NULLCHAR && *s != '\033') {
1955 if (*s == '(' || *s == '[') {
1966 /* Remove all highlighting escape sequences in s */
1968 StripHighlight (char *s)
1970 static char retbuf[MSG_SIZ];
1973 while (*s != NULLCHAR) {
1974 while (*s == '\033') {
1975 while (*s != NULLCHAR && !isalpha(*s)) s++;
1976 if (*s != NULLCHAR) s++;
1978 while (*s != NULLCHAR && *s != '\033') {
1986 char *variantNames[] = VARIANT_NAMES;
1988 VariantName (VariantClass v)
1990 return variantNames[v];
1994 /* Identify a variant from the strings the chess servers use or the
1995 PGN Variant tag names we use. */
1997 StringToVariant (char *e)
2001 VariantClass v = VariantNormal;
2002 int i, found = FALSE;
2008 /* [HGM] skip over optional board-size prefixes */
2009 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2010 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2011 while( *e++ != '_');
2014 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2018 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2019 if (StrCaseStr(e, variantNames[i])) {
2020 v = (VariantClass) i;
2027 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2028 || StrCaseStr(e, "wild/fr")
2029 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2030 v = VariantFischeRandom;
2031 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2032 (i = 1, p = StrCaseStr(e, "w"))) {
2034 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2041 case 0: /* FICS only, actually */
2043 /* Castling legal even if K starts on d-file */
2044 v = VariantWildCastle;
2049 /* Castling illegal even if K & R happen to start in
2050 normal positions. */
2051 v = VariantNoCastle;
2064 /* Castling legal iff K & R start in normal positions */
2070 /* Special wilds for position setup; unclear what to do here */
2071 v = VariantLoadable;
2074 /* Bizarre ICC game */
2075 v = VariantTwoKings;
2078 v = VariantKriegspiel;
2084 v = VariantFischeRandom;
2087 v = VariantCrazyhouse;
2090 v = VariantBughouse;
2096 /* Not quite the same as FICS suicide! */
2097 v = VariantGiveaway;
2103 v = VariantShatranj;
2106 /* Temporary names for future ICC types. The name *will* change in
2107 the next xboard/WinBoard release after ICC defines it. */
2145 v = VariantCapablanca;
2148 v = VariantKnightmate;
2154 v = VariantCylinder;
2160 v = VariantCapaRandom;
2163 v = VariantBerolina;
2175 /* Found "wild" or "w" in the string but no number;
2176 must assume it's normal chess. */
2180 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2181 if( (len >= MSG_SIZ) && appData.debugMode )
2182 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2184 DisplayError(buf, 0);
2190 if (appData.debugMode) {
2191 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2192 e, wnum, VariantName(v));
2197 static int leftover_start = 0, leftover_len = 0;
2198 char star_match[STAR_MATCH_N][MSG_SIZ];
2200 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2201 advance *index beyond it, and set leftover_start to the new value of
2202 *index; else return FALSE. If pattern contains the character '*', it
2203 matches any sequence of characters not containing '\r', '\n', or the
2204 character following the '*' (if any), and the matched sequence(s) are
2205 copied into star_match.
2208 looking_at ( char *buf, int *index, char *pattern)
2210 char *bufp = &buf[*index], *patternp = pattern;
2212 char *matchp = star_match[0];
2215 if (*patternp == NULLCHAR) {
2216 *index = leftover_start = bufp - buf;
2220 if (*bufp == NULLCHAR) return FALSE;
2221 if (*patternp == '*') {
2222 if (*bufp == *(patternp + 1)) {
2224 matchp = star_match[++star_count];
2228 } else if (*bufp == '\n' || *bufp == '\r') {
2230 if (*patternp == NULLCHAR)
2235 *matchp++ = *bufp++;
2239 if (*patternp != *bufp) return FALSE;
2246 SendToPlayer (char *data, int length)
2248 int error, outCount;
2249 outCount = OutputToProcess(NoProc, data, length, &error);
2250 if (outCount < length) {
2251 DisplayFatalError(_("Error writing to display"), error, 1);
2256 PackHolding (char packed[], char *holding)
2266 switch (runlength) {
2277 sprintf(q, "%d", runlength);
2289 /* Telnet protocol requests from the front end */
2291 TelnetRequest (unsigned char ddww, unsigned char option)
2293 unsigned char msg[3];
2294 int outCount, outError;
2296 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2298 if (appData.debugMode) {
2299 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2315 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2324 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2327 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2332 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2334 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2341 if (!appData.icsActive) return;
2342 TelnetRequest(TN_DO, TN_ECHO);
2348 if (!appData.icsActive) return;
2349 TelnetRequest(TN_DONT, TN_ECHO);
2353 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2355 /* put the holdings sent to us by the server on the board holdings area */
2356 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2360 if(gameInfo.holdingsWidth < 2) return;
2361 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2362 return; // prevent overwriting by pre-board holdings
2364 if( (int)lowestPiece >= BlackPawn ) {
2367 holdingsStartRow = BOARD_HEIGHT-1;
2370 holdingsColumn = BOARD_WIDTH-1;
2371 countsColumn = BOARD_WIDTH-2;
2372 holdingsStartRow = 0;
2376 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2377 board[i][holdingsColumn] = EmptySquare;
2378 board[i][countsColumn] = (ChessSquare) 0;
2380 while( (p=*holdings++) != NULLCHAR ) {
2381 piece = CharToPiece( ToUpper(p) );
2382 if(piece == EmptySquare) continue;
2383 /*j = (int) piece - (int) WhitePawn;*/
2384 j = PieceToNumber(piece);
2385 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2386 if(j < 0) continue; /* should not happen */
2387 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2388 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2389 board[holdingsStartRow+j*direction][countsColumn]++;
2395 VariantSwitch (Board board, VariantClass newVariant)
2397 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2398 static Board oldBoard;
2400 startedFromPositionFile = FALSE;
2401 if(gameInfo.variant == newVariant) return;
2403 /* [HGM] This routine is called each time an assignment is made to
2404 * gameInfo.variant during a game, to make sure the board sizes
2405 * are set to match the new variant. If that means adding or deleting
2406 * holdings, we shift the playing board accordingly
2407 * This kludge is needed because in ICS observe mode, we get boards
2408 * of an ongoing game without knowing the variant, and learn about the
2409 * latter only later. This can be because of the move list we requested,
2410 * in which case the game history is refilled from the beginning anyway,
2411 * but also when receiving holdings of a crazyhouse game. In the latter
2412 * case we want to add those holdings to the already received position.
2416 if (appData.debugMode) {
2417 fprintf(debugFP, "Switch board from %s to %s\n",
2418 VariantName(gameInfo.variant), VariantName(newVariant));
2419 setbuf(debugFP, NULL);
2421 shuffleOpenings = 0; /* [HGM] shuffle */
2422 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2426 newWidth = 9; newHeight = 9;
2427 gameInfo.holdingsSize = 7;
2428 case VariantBughouse:
2429 case VariantCrazyhouse:
2430 newHoldingsWidth = 2; break;
2434 newHoldingsWidth = 2;
2435 gameInfo.holdingsSize = 8;
2438 case VariantCapablanca:
2439 case VariantCapaRandom:
2442 newHoldingsWidth = gameInfo.holdingsSize = 0;
2445 if(newWidth != gameInfo.boardWidth ||
2446 newHeight != gameInfo.boardHeight ||
2447 newHoldingsWidth != gameInfo.holdingsWidth ) {
2449 /* shift position to new playing area, if needed */
2450 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2451 for(i=0; i<BOARD_HEIGHT; i++)
2452 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2453 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2455 for(i=0; i<newHeight; i++) {
2456 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2457 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2459 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2460 for(i=0; i<BOARD_HEIGHT; i++)
2461 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2462 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2465 board[HOLDINGS_SET] = 0;
2466 gameInfo.boardWidth = newWidth;
2467 gameInfo.boardHeight = newHeight;
2468 gameInfo.holdingsWidth = newHoldingsWidth;
2469 gameInfo.variant = newVariant;
2470 InitDrawingSizes(-2, 0);
2471 } else gameInfo.variant = newVariant;
2472 CopyBoard(oldBoard, board); // remember correctly formatted board
2473 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2474 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2477 static int loggedOn = FALSE;
2479 /*-- Game start info cache: --*/
2481 char gs_kind[MSG_SIZ];
2482 static char player1Name[128] = "";
2483 static char player2Name[128] = "";
2484 static char cont_seq[] = "\n\\ ";
2485 static int player1Rating = -1;
2486 static int player2Rating = -1;
2487 /*----------------------------*/
2489 ColorClass curColor = ColorNormal;
2490 int suppressKibitz = 0;
2493 Boolean soughtPending = FALSE;
2494 Boolean seekGraphUp;
2495 #define MAX_SEEK_ADS 200
2497 char *seekAdList[MAX_SEEK_ADS];
2498 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2499 float tcList[MAX_SEEK_ADS];
2500 char colorList[MAX_SEEK_ADS];
2501 int nrOfSeekAds = 0;
2502 int minRating = 1010, maxRating = 2800;
2503 int hMargin = 10, vMargin = 20, h, w;
2504 extern int squareSize, lineGap;
2509 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2510 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2511 if(r < minRating+100 && r >=0 ) r = minRating+100;
2512 if(r > maxRating) r = maxRating;
2513 if(tc < 1.f) tc = 1.f;
2514 if(tc > 95.f) tc = 95.f;
2515 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2516 y = ((double)r - minRating)/(maxRating - minRating)
2517 * (h-vMargin-squareSize/8-1) + vMargin;
2518 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2519 if(strstr(seekAdList[i], " u ")) color = 1;
2520 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2521 !strstr(seekAdList[i], "bullet") &&
2522 !strstr(seekAdList[i], "blitz") &&
2523 !strstr(seekAdList[i], "standard") ) color = 2;
2524 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2525 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2529 PlotSingleSeekAd (int i)
2535 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2537 char buf[MSG_SIZ], *ext = "";
2538 VariantClass v = StringToVariant(type);
2539 if(strstr(type, "wild")) {
2540 ext = type + 4; // append wild number
2541 if(v == VariantFischeRandom) type = "chess960"; else
2542 if(v == VariantLoadable) type = "setup"; else
2543 type = VariantName(v);
2545 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2546 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2547 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2548 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2549 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2550 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2551 seekNrList[nrOfSeekAds] = nr;
2552 zList[nrOfSeekAds] = 0;
2553 seekAdList[nrOfSeekAds++] = StrSave(buf);
2554 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2559 EraseSeekDot (int i)
2561 int x = xList[i], y = yList[i], d=squareSize/4, k;
2562 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2563 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2564 // now replot every dot that overlapped
2565 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2566 int xx = xList[k], yy = yList[k];
2567 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2568 DrawSeekDot(xx, yy, colorList[k]);
2573 RemoveSeekAd (int nr)
2576 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2578 if(seekAdList[i]) free(seekAdList[i]);
2579 seekAdList[i] = seekAdList[--nrOfSeekAds];
2580 seekNrList[i] = seekNrList[nrOfSeekAds];
2581 ratingList[i] = ratingList[nrOfSeekAds];
2582 colorList[i] = colorList[nrOfSeekAds];
2583 tcList[i] = tcList[nrOfSeekAds];
2584 xList[i] = xList[nrOfSeekAds];
2585 yList[i] = yList[nrOfSeekAds];
2586 zList[i] = zList[nrOfSeekAds];
2587 seekAdList[nrOfSeekAds] = NULL;
2593 MatchSoughtLine (char *line)
2595 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2596 int nr, base, inc, u=0; char dummy;
2598 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2599 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2601 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2602 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2603 // match: compact and save the line
2604 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2614 if(!seekGraphUp) return FALSE;
2615 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2616 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2618 DrawSeekBackground(0, 0, w, h);
2619 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2620 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2621 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2622 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2624 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2627 snprintf(buf, MSG_SIZ, "%d", i);
2628 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2631 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2632 for(i=1; i<100; i+=(i<10?1:5)) {
2633 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2634 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2635 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2637 snprintf(buf, MSG_SIZ, "%d", i);
2638 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2641 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2646 SeekGraphClick (ClickType click, int x, int y, int moving)
2648 static int lastDown = 0, displayed = 0, lastSecond;
2649 if(y < 0) return FALSE;
2650 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2651 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2652 if(!seekGraphUp) return FALSE;
2653 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2654 DrawPosition(TRUE, NULL);
2657 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2658 if(click == Release || moving) return FALSE;
2660 soughtPending = TRUE;
2661 SendToICS(ics_prefix);
2662 SendToICS("sought\n"); // should this be "sought all"?
2663 } else { // issue challenge based on clicked ad
2664 int dist = 10000; int i, closest = 0, second = 0;
2665 for(i=0; i<nrOfSeekAds; i++) {
2666 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2667 if(d < dist) { dist = d; closest = i; }
2668 second += (d - zList[i] < 120); // count in-range ads
2669 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2673 second = (second > 1);
2674 if(displayed != closest || second != lastSecond) {
2675 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2676 lastSecond = second; displayed = closest;
2678 if(click == Press) {
2679 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2682 } // on press 'hit', only show info
2683 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2684 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2685 SendToICS(ics_prefix);
2687 return TRUE; // let incoming board of started game pop down the graph
2688 } else if(click == Release) { // release 'miss' is ignored
2689 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2690 if(moving == 2) { // right up-click
2691 nrOfSeekAds = 0; // refresh graph
2692 soughtPending = TRUE;
2693 SendToICS(ics_prefix);
2694 SendToICS("sought\n"); // should this be "sought all"?
2697 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2698 // press miss or release hit 'pop down' seek graph
2699 seekGraphUp = FALSE;
2700 DrawPosition(TRUE, NULL);
2706 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2708 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2709 #define STARTED_NONE 0
2710 #define STARTED_MOVES 1
2711 #define STARTED_BOARD 2
2712 #define STARTED_OBSERVE 3
2713 #define STARTED_HOLDINGS 4
2714 #define STARTED_CHATTER 5
2715 #define STARTED_COMMENT 6
2716 #define STARTED_MOVES_NOHIDE 7
2718 static int started = STARTED_NONE;
2719 static char parse[20000];
2720 static int parse_pos = 0;
2721 static char buf[BUF_SIZE + 1];
2722 static int firstTime = TRUE, intfSet = FALSE;
2723 static ColorClass prevColor = ColorNormal;
2724 static int savingComment = FALSE;
2725 static int cmatch = 0; // continuation sequence match
2732 int backup; /* [DM] For zippy color lines */
2734 char talker[MSG_SIZ]; // [HGM] chat
2737 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2739 if (appData.debugMode) {
2741 fprintf(debugFP, "<ICS: ");
2742 show_bytes(debugFP, data, count);
2743 fprintf(debugFP, "\n");
2747 if (appData.debugMode) { int f = forwardMostMove;
2748 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2749 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2750 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2753 /* If last read ended with a partial line that we couldn't parse,
2754 prepend it to the new read and try again. */
2755 if (leftover_len > 0) {
2756 for (i=0; i<leftover_len; i++)
2757 buf[i] = buf[leftover_start + i];
2760 /* copy new characters into the buffer */
2761 bp = buf + leftover_len;
2762 buf_len=leftover_len;
2763 for (i=0; i<count; i++)
2766 if (data[i] == '\r')
2769 // join lines split by ICS?
2770 if (!appData.noJoin)
2773 Joining just consists of finding matches against the
2774 continuation sequence, and discarding that sequence
2775 if found instead of copying it. So, until a match
2776 fails, there's nothing to do since it might be the
2777 complete sequence, and thus, something we don't want
2780 if (data[i] == cont_seq[cmatch])
2783 if (cmatch == strlen(cont_seq))
2785 cmatch = 0; // complete match. just reset the counter
2788 it's possible for the ICS to not include the space
2789 at the end of the last word, making our [correct]
2790 join operation fuse two separate words. the server
2791 does this when the space occurs at the width setting.
2793 if (!buf_len || buf[buf_len-1] != ' ')
2804 match failed, so we have to copy what matched before
2805 falling through and copying this character. In reality,
2806 this will only ever be just the newline character, but
2807 it doesn't hurt to be precise.
2809 strncpy(bp, cont_seq, cmatch);
2821 buf[buf_len] = NULLCHAR;
2822 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2827 while (i < buf_len) {
2828 /* Deal with part of the TELNET option negotiation
2829 protocol. We refuse to do anything beyond the
2830 defaults, except that we allow the WILL ECHO option,
2831 which ICS uses to turn off password echoing when we are
2832 directly connected to it. We reject this option
2833 if localLineEditing mode is on (always on in xboard)
2834 and we are talking to port 23, which might be a real
2835 telnet server that will try to keep WILL ECHO on permanently.
2837 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2838 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2839 unsigned char option;
2841 switch ((unsigned char) buf[++i]) {
2843 if (appData.debugMode)
2844 fprintf(debugFP, "\n<WILL ");
2845 switch (option = (unsigned char) buf[++i]) {
2847 if (appData.debugMode)
2848 fprintf(debugFP, "ECHO ");
2849 /* Reply only if this is a change, according
2850 to the protocol rules. */
2851 if (remoteEchoOption) break;
2852 if (appData.localLineEditing &&
2853 atoi(appData.icsPort) == TN_PORT) {
2854 TelnetRequest(TN_DONT, TN_ECHO);
2857 TelnetRequest(TN_DO, TN_ECHO);
2858 remoteEchoOption = TRUE;
2862 if (appData.debugMode)
2863 fprintf(debugFP, "%d ", option);
2864 /* Whatever this is, we don't want it. */
2865 TelnetRequest(TN_DONT, option);
2870 if (appData.debugMode)
2871 fprintf(debugFP, "\n<WONT ");
2872 switch (option = (unsigned char) buf[++i]) {
2874 if (appData.debugMode)
2875 fprintf(debugFP, "ECHO ");
2876 /* Reply only if this is a change, according
2877 to the protocol rules. */
2878 if (!remoteEchoOption) break;
2880 TelnetRequest(TN_DONT, TN_ECHO);
2881 remoteEchoOption = FALSE;
2884 if (appData.debugMode)
2885 fprintf(debugFP, "%d ", (unsigned char) option);
2886 /* Whatever this is, it must already be turned
2887 off, because we never agree to turn on
2888 anything non-default, so according to the
2889 protocol rules, we don't reply. */
2894 if (appData.debugMode)
2895 fprintf(debugFP, "\n<DO ");
2896 switch (option = (unsigned char) buf[++i]) {
2898 /* Whatever this is, we refuse to do it. */
2899 if (appData.debugMode)
2900 fprintf(debugFP, "%d ", option);
2901 TelnetRequest(TN_WONT, option);
2906 if (appData.debugMode)
2907 fprintf(debugFP, "\n<DONT ");
2908 switch (option = (unsigned char) buf[++i]) {
2910 if (appData.debugMode)
2911 fprintf(debugFP, "%d ", option);
2912 /* Whatever this is, we are already not doing
2913 it, because we never agree to do anything
2914 non-default, so according to the protocol
2915 rules, we don't reply. */
2920 if (appData.debugMode)
2921 fprintf(debugFP, "\n<IAC ");
2922 /* Doubled IAC; pass it through */
2926 if (appData.debugMode)
2927 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2928 /* Drop all other telnet commands on the floor */
2931 if (oldi > next_out)
2932 SendToPlayer(&buf[next_out], oldi - next_out);
2938 /* OK, this at least will *usually* work */
2939 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2943 if (loggedOn && !intfSet) {
2944 if (ics_type == ICS_ICC) {
2945 snprintf(str, MSG_SIZ,
2946 "/set-quietly interface %s\n/set-quietly style 12\n",
2948 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2949 strcat(str, "/set-2 51 1\n/set seek 1\n");
2950 } else if (ics_type == ICS_CHESSNET) {
2951 snprintf(str, MSG_SIZ, "/style 12\n");
2953 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2954 strcat(str, programVersion);
2955 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2956 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2957 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2959 strcat(str, "$iset nohighlight 1\n");
2961 strcat(str, "$iset lock 1\n$style 12\n");
2964 NotifyFrontendLogin();
2968 if (started == STARTED_COMMENT) {
2969 /* Accumulate characters in comment */
2970 parse[parse_pos++] = buf[i];
2971 if (buf[i] == '\n') {
2972 parse[parse_pos] = NULLCHAR;
2973 if(chattingPartner>=0) {
2975 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2976 OutputChatMessage(chattingPartner, mess);
2977 chattingPartner = -1;
2978 next_out = i+1; // [HGM] suppress printing in ICS window
2980 if(!suppressKibitz) // [HGM] kibitz
2981 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2982 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2983 int nrDigit = 0, nrAlph = 0, j;
2984 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2985 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2986 parse[parse_pos] = NULLCHAR;
2987 // try to be smart: if it does not look like search info, it should go to
2988 // ICS interaction window after all, not to engine-output window.
2989 for(j=0; j<parse_pos; j++) { // count letters and digits
2990 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2991 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2992 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2994 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2995 int depth=0; float score;
2996 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2997 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2998 pvInfoList[forwardMostMove-1].depth = depth;
2999 pvInfoList[forwardMostMove-1].score = 100*score;
3001 OutputKibitz(suppressKibitz, parse);
3004 if(gameMode == IcsObserving) // restore original ICS messages
3005 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3007 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3008 SendToPlayer(tmp, strlen(tmp));
3010 next_out = i+1; // [HGM] suppress printing in ICS window
3012 started = STARTED_NONE;
3014 /* Don't match patterns against characters in comment */
3019 if (started == STARTED_CHATTER) {
3020 if (buf[i] != '\n') {
3021 /* Don't match patterns against characters in chatter */
3025 started = STARTED_NONE;
3026 if(suppressKibitz) next_out = i+1;
3029 /* Kludge to deal with rcmd protocol */
3030 if (firstTime && looking_at(buf, &i, "\001*")) {
3031 DisplayFatalError(&buf[1], 0, 1);
3037 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3040 if (appData.debugMode)
3041 fprintf(debugFP, "ics_type %d\n", ics_type);
3044 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3045 ics_type = ICS_FICS;
3047 if (appData.debugMode)
3048 fprintf(debugFP, "ics_type %d\n", ics_type);
3051 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3052 ics_type = ICS_CHESSNET;
3054 if (appData.debugMode)
3055 fprintf(debugFP, "ics_type %d\n", ics_type);
3060 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3061 looking_at(buf, &i, "Logging you in as \"*\"") ||
3062 looking_at(buf, &i, "will be \"*\""))) {
3063 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3067 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3069 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3070 DisplayIcsInteractionTitle(buf);
3071 have_set_title = TRUE;
3074 /* skip finger notes */
3075 if (started == STARTED_NONE &&
3076 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3077 (buf[i] == '1' && buf[i+1] == '0')) &&
3078 buf[i+2] == ':' && buf[i+3] == ' ') {
3079 started = STARTED_CHATTER;
3085 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3086 if(appData.seekGraph) {
3087 if(soughtPending && MatchSoughtLine(buf+i)) {
3088 i = strstr(buf+i, "rated") - buf;
3089 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090 next_out = leftover_start = i;
3091 started = STARTED_CHATTER;
3092 suppressKibitz = TRUE;
3095 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3096 && looking_at(buf, &i, "* ads displayed")) {
3097 soughtPending = FALSE;
3102 if(appData.autoRefresh) {
3103 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3104 int s = (ics_type == ICS_ICC); // ICC format differs
3106 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3107 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3108 looking_at(buf, &i, "*% "); // eat prompt
3109 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3110 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111 next_out = i; // suppress
3114 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3115 char *p = star_match[0];
3117 if(seekGraphUp) RemoveSeekAd(atoi(p));
3118 while(*p && *p++ != ' '); // next
3120 looking_at(buf, &i, "*% "); // eat prompt
3121 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128 /* skip formula vars */
3129 if (started == STARTED_NONE &&
3130 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3131 started = STARTED_CHATTER;
3136 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3137 if (appData.autoKibitz && started == STARTED_NONE &&
3138 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3139 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3140 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3141 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3142 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3143 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3144 suppressKibitz = TRUE;
3145 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3147 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3148 && (gameMode == IcsPlayingWhite)) ||
3149 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3150 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3151 started = STARTED_CHATTER; // own kibitz we simply discard
3153 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3154 parse_pos = 0; parse[0] = NULLCHAR;
3155 savingComment = TRUE;
3156 suppressKibitz = gameMode != IcsObserving ? 2 :
3157 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3161 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3162 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3163 && atoi(star_match[0])) {
3164 // suppress the acknowledgements of our own autoKibitz
3166 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3168 SendToPlayer(star_match[0], strlen(star_match[0]));
3169 if(looking_at(buf, &i, "*% ")) // eat prompt
3170 suppressKibitz = FALSE;
3174 } // [HGM] kibitz: end of patch
3176 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3178 // [HGM] chat: intercept tells by users for which we have an open chat window
3180 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3181 looking_at(buf, &i, "* whispers:") ||
3182 looking_at(buf, &i, "* kibitzes:") ||
3183 looking_at(buf, &i, "* shouts:") ||
3184 looking_at(buf, &i, "* c-shouts:") ||
3185 looking_at(buf, &i, "--> * ") ||
3186 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3187 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3188 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3189 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3191 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3192 chattingPartner = -1;
3194 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3195 for(p=0; p<MAX_CHAT; p++) {
3196 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3197 talker[0] = '['; strcat(talker, "] ");
3198 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3199 chattingPartner = p; break;
3202 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3203 for(p=0; p<MAX_CHAT; p++) {
3204 if(!strcmp("kibitzes", chatPartner[p])) {
3205 talker[0] = '['; strcat(talker, "] ");
3206 chattingPartner = p; break;
3209 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3210 for(p=0; p<MAX_CHAT; p++) {
3211 if(!strcmp("whispers", chatPartner[p])) {
3212 talker[0] = '['; strcat(talker, "] ");
3213 chattingPartner = p; break;
3216 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3217 if(buf[i-8] == '-' && buf[i-3] == 't')
3218 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3219 if(!strcmp("c-shouts", chatPartner[p])) {
3220 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3221 chattingPartner = p; break;
3224 if(chattingPartner < 0)
3225 for(p=0; p<MAX_CHAT; p++) {
3226 if(!strcmp("shouts", chatPartner[p])) {
3227 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3228 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3229 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3230 chattingPartner = p; break;
3234 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3235 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3236 talker[0] = 0; Colorize(ColorTell, FALSE);
3237 chattingPartner = p; break;
3239 if(chattingPartner<0) i = oldi; else {
3240 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3241 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3242 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243 started = STARTED_COMMENT;
3244 parse_pos = 0; parse[0] = NULLCHAR;
3245 savingComment = 3 + chattingPartner; // counts as TRUE
3246 suppressKibitz = TRUE;
3249 } // [HGM] chat: end of patch
3252 if (appData.zippyTalk || appData.zippyPlay) {
3253 /* [DM] Backup address for color zippy lines */
3255 if (loggedOn == TRUE)
3256 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3257 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3259 } // [DM] 'else { ' deleted
3261 /* Regular tells and says */
3262 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3263 looking_at(buf, &i, "* (your partner) tells you: ") ||
3264 looking_at(buf, &i, "* says: ") ||
3265 /* Don't color "message" or "messages" output */
3266 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3267 looking_at(buf, &i, "*. * at *:*: ") ||
3268 looking_at(buf, &i, "--* (*:*): ") ||
3269 /* Message notifications (same color as tells) */
3270 looking_at(buf, &i, "* has left a message ") ||
3271 looking_at(buf, &i, "* just sent you a message:\n") ||
3272 /* Whispers and kibitzes */
3273 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3274 looking_at(buf, &i, "* kibitzes: ") ||
3276 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3278 if (tkind == 1 && strchr(star_match[0], ':')) {
3279 /* Avoid "tells you:" spoofs in channels */
3282 if (star_match[0][0] == NULLCHAR ||
3283 strchr(star_match[0], ' ') ||
3284 (tkind == 3 && strchr(star_match[1], ' '))) {
3285 /* Reject bogus matches */
3288 if (appData.colorize) {
3289 if (oldi > next_out) {
3290 SendToPlayer(&buf[next_out], oldi - next_out);
3295 Colorize(ColorTell, FALSE);
3296 curColor = ColorTell;
3299 Colorize(ColorKibitz, FALSE);
3300 curColor = ColorKibitz;
3303 p = strrchr(star_match[1], '(');
3310 Colorize(ColorChannel1, FALSE);
3311 curColor = ColorChannel1;
3313 Colorize(ColorChannel, FALSE);
3314 curColor = ColorChannel;
3318 curColor = ColorNormal;
3322 if (started == STARTED_NONE && appData.autoComment &&
3323 (gameMode == IcsObserving ||
3324 gameMode == IcsPlayingWhite ||
3325 gameMode == IcsPlayingBlack)) {
3326 parse_pos = i - oldi;
3327 memcpy(parse, &buf[oldi], parse_pos);
3328 parse[parse_pos] = NULLCHAR;
3329 started = STARTED_COMMENT;
3330 savingComment = TRUE;
3332 started = STARTED_CHATTER;
3333 savingComment = FALSE;
3340 if (looking_at(buf, &i, "* s-shouts: ") ||
3341 looking_at(buf, &i, "* c-shouts: ")) {
3342 if (appData.colorize) {
3343 if (oldi > next_out) {
3344 SendToPlayer(&buf[next_out], oldi - next_out);
3347 Colorize(ColorSShout, FALSE);
3348 curColor = ColorSShout;
3351 started = STARTED_CHATTER;
3355 if (looking_at(buf, &i, "--->")) {
3360 if (looking_at(buf, &i, "* shouts: ") ||
3361 looking_at(buf, &i, "--> ")) {
3362 if (appData.colorize) {
3363 if (oldi > next_out) {
3364 SendToPlayer(&buf[next_out], oldi - next_out);
3367 Colorize(ColorShout, FALSE);
3368 curColor = ColorShout;
3371 started = STARTED_CHATTER;
3375 if (looking_at( buf, &i, "Challenge:")) {
3376 if (appData.colorize) {
3377 if (oldi > next_out) {
3378 SendToPlayer(&buf[next_out], oldi - next_out);
3381 Colorize(ColorChallenge, FALSE);
3382 curColor = ColorChallenge;
3388 if (looking_at(buf, &i, "* offers you") ||
3389 looking_at(buf, &i, "* offers to be") ||
3390 looking_at(buf, &i, "* would like to") ||
3391 looking_at(buf, &i, "* requests to") ||
3392 looking_at(buf, &i, "Your opponent offers") ||
3393 looking_at(buf, &i, "Your opponent requests")) {
3395 if (appData.colorize) {
3396 if (oldi > next_out) {
3397 SendToPlayer(&buf[next_out], oldi - next_out);
3400 Colorize(ColorRequest, FALSE);
3401 curColor = ColorRequest;
3406 if (looking_at(buf, &i, "* (*) seeking")) {
3407 if (appData.colorize) {
3408 if (oldi > next_out) {
3409 SendToPlayer(&buf[next_out], oldi - next_out);
3412 Colorize(ColorSeek, FALSE);
3413 curColor = ColorSeek;