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 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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
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 */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy( char *dst, const char *src, size_t count )
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble(u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
398 [AS] Note: sometimes, the sscanf() function is used to parse the input
399 into a fixed-size buffer. Because of this, we must be prepared to
400 receive strings as long as the size of the input buffer, which is currently
401 set to 4K for Windows and 8K for the rest.
402 So, we must either allocate sufficiently large buffers here, or
403 reduce the size of the input buffer in the input reading part.
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
410 ChessProgramState first, second;
412 /* premove variables */
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
465 /* animateTraining preserves the state of appData.animate
466 * when Training mode is activated. This allows the
467 * response to be animated when appData.animate == TRUE and
468 * appData.animateDragging == TRUE.
470 Boolean animateTraining;
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char initialRights[BOARD_FILES];
480 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int initialRulePlies, FENrulePlies;
482 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int mute; // mute all sounds
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
501 ChessSquare FIDEArray[2][BOARD_FILES] = {
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505 BlackKing, BlackBishop, BlackKnight, BlackRook }
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackKing, BlackKnight, BlackRook }
515 ChessSquare KnightmateArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518 { BlackRook, BlackMan, BlackBishop, BlackQueen,
519 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackMan, BlackFerz,
547 BlackKing, BlackMan, BlackKnight, BlackRook }
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
595 #define GothicArray CapablancaArray
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
606 #define FalconArray CapablancaArray
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
628 Board initialPosition;
631 /* Convert str to a rating. Checks for special cases of "----",
633 "++++", etc. Also strips ()'s */
635 string_to_rating(str)
638 while(*str && !isdigit(*str)) ++str;
640 return 0; /* One of the special "no rating" cases */
648 /* Init programStats */
649 programStats.movelist[0] = 0;
650 programStats.depth = 0;
651 programStats.nr_moves = 0;
652 programStats.moves_left = 0;
653 programStats.nodes = 0;
654 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
655 programStats.score = 0;
656 programStats.got_only_move = 0;
657 programStats.got_fail = 0;
658 programStats.line_is_book = 0;
664 int matched, min, sec;
666 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
667 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
669 GetTimeMark(&programStartTime);
670 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
673 programStats.ok_to_send = 1;
674 programStats.seen_stat = 0;
677 * Initialize game list
683 * Internet chess server status
685 if (appData.icsActive) {
686 appData.matchMode = FALSE;
687 appData.matchGames = 0;
689 appData.noChessProgram = !appData.zippyPlay;
691 appData.zippyPlay = FALSE;
692 appData.zippyTalk = FALSE;
693 appData.noChessProgram = TRUE;
695 if (*appData.icsHelper != NULLCHAR) {
696 appData.useTelnet = TRUE;
697 appData.telnetProgram = appData.icsHelper;
700 appData.zippyTalk = appData.zippyPlay = FALSE;
703 /* [AS] Initialize pv info list [HGM] and game state */
707 for( i=0; i<=framePtr; i++ ) {
708 pvInfoList[i].depth = -1;
709 boards[i][EP_STATUS] = EP_NONE;
710 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
715 * Parse timeControl resource
717 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
718 appData.movesPerSession)) {
720 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
721 DisplayFatalError(buf, 0, 2);
725 * Parse searchTime resource
727 if (*appData.searchTime != NULLCHAR) {
728 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
730 searchTime = min * 60;
731 } else if (matched == 2) {
732 searchTime = min * 60 + sec;
735 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
736 DisplayFatalError(buf, 0, 2);
740 /* [AS] Adjudication threshold */
741 adjudicateLossThreshold = appData.adjudicateLossThreshold;
743 first.which = "first";
744 second.which = "second";
745 first.maybeThinking = second.maybeThinking = FALSE;
746 first.pr = second.pr = NoProc;
747 first.isr = second.isr = NULL;
748 first.sendTime = second.sendTime = 2;
749 first.sendDrawOffers = 1;
750 if (appData.firstPlaysBlack) {
751 first.twoMachinesColor = "black\n";
752 second.twoMachinesColor = "white\n";
754 first.twoMachinesColor = "white\n";
755 second.twoMachinesColor = "black\n";
757 first.program = appData.firstChessProgram;
758 second.program = appData.secondChessProgram;
759 first.host = appData.firstHost;
760 second.host = appData.secondHost;
761 first.dir = appData.firstDirectory;
762 second.dir = appData.secondDirectory;
763 first.other = &second;
764 second.other = &first;
765 first.initString = appData.initString;
766 second.initString = appData.secondInitString;
767 first.computerString = appData.firstComputerString;
768 second.computerString = appData.secondComputerString;
769 first.useSigint = second.useSigint = TRUE;
770 first.useSigterm = second.useSigterm = TRUE;
771 first.reuse = appData.reuseFirst;
772 second.reuse = appData.reuseSecond;
773 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
774 second.nps = appData.secondNPS;
775 first.useSetboard = second.useSetboard = FALSE;
776 first.useSAN = second.useSAN = FALSE;
777 first.usePing = second.usePing = FALSE;
778 first.lastPing = second.lastPing = 0;
779 first.lastPong = second.lastPong = 0;
780 first.usePlayother = second.usePlayother = FALSE;
781 first.useColors = second.useColors = TRUE;
782 first.useUsermove = second.useUsermove = FALSE;
783 first.sendICS = second.sendICS = FALSE;
784 first.sendName = second.sendName = appData.icsActive;
785 first.sdKludge = second.sdKludge = FALSE;
786 first.stKludge = second.stKludge = FALSE;
787 TidyProgramName(first.program, first.host, first.tidy);
788 TidyProgramName(second.program, second.host, second.tidy);
789 first.matchWins = second.matchWins = 0;
790 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
791 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
792 first.analysisSupport = second.analysisSupport = 2; /* detect */
793 first.analyzing = second.analyzing = FALSE;
794 first.initDone = second.initDone = FALSE;
796 /* New features added by Tord: */
797 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
798 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
799 /* End of new features added by Tord. */
800 first.fenOverride = appData.fenOverride1;
801 second.fenOverride = appData.fenOverride2;
803 /* [HGM] time odds: set factor for each machine */
804 first.timeOdds = appData.firstTimeOdds;
805 second.timeOdds = appData.secondTimeOdds;
807 if(appData.timeOddsMode) {
808 norm = first.timeOdds;
809 if(norm > second.timeOdds) norm = second.timeOdds;
811 first.timeOdds /= norm;
812 second.timeOdds /= norm;
815 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
816 first.accumulateTC = appData.firstAccumulateTC;
817 second.accumulateTC = appData.secondAccumulateTC;
818 first.maxNrOfSessions = second.maxNrOfSessions = 1;
821 first.debug = second.debug = FALSE;
822 first.supportsNPS = second.supportsNPS = UNKNOWN;
825 first.optionSettings = appData.firstOptions;
826 second.optionSettings = appData.secondOptions;
828 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
829 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
830 first.isUCI = appData.firstIsUCI; /* [AS] */
831 second.isUCI = appData.secondIsUCI; /* [AS] */
832 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
833 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
835 if (appData.firstProtocolVersion > PROTOVER
836 || appData.firstProtocolVersion < 1)
841 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
842 appData.firstProtocolVersion);
843 if( (len > MSG_SIZ) && appData.debugMode )
844 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
846 DisplayFatalError(buf, 0, 2);
850 first.protocolVersion = appData.firstProtocolVersion;
853 if (appData.secondProtocolVersion > PROTOVER
854 || appData.secondProtocolVersion < 1)
859 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860 appData.secondProtocolVersion);
861 if( (len > MSG_SIZ) && appData.debugMode )
862 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
864 DisplayFatalError(buf, 0, 2);
868 second.protocolVersion = appData.secondProtocolVersion;
871 if (appData.icsActive) {
872 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
873 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
874 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
875 appData.clockMode = FALSE;
876 first.sendTime = second.sendTime = 0;
880 /* Override some settings from environment variables, for backward
881 compatibility. Unfortunately it's not feasible to have the env
882 vars just set defaults, at least in xboard. Ugh.
884 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
889 if (appData.noChessProgram) {
890 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
891 sprintf(programVersion, "%s", PACKAGE_STRING);
893 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
894 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
895 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
898 if (!appData.icsActive) {
902 /* Check for variants that are supported only in ICS mode,
903 or not at all. Some that are accepted here nevertheless
904 have bugs; see comments below.
906 VariantClass variant = StringToVariant(appData.variant);
908 case VariantBughouse: /* need four players and two boards */
909 case VariantKriegspiel: /* need to hide pieces and move details */
910 /* case VariantFischeRandom: (Fabien: moved below) */
911 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
912 if( (len > MSG_SIZ) && appData.debugMode )
913 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
915 DisplayFatalError(buf, 0, 2);
919 case VariantLoadable:
929 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
930 if( (len > MSG_SIZ) && appData.debugMode )
931 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
933 DisplayFatalError(buf, 0, 2);
936 case VariantXiangqi: /* [HGM] repetition rules not implemented */
937 case VariantFairy: /* [HGM] TestLegality definitely off! */
938 case VariantGothic: /* [HGM] should work */
939 case VariantCapablanca: /* [HGM] should work */
940 case VariantCourier: /* [HGM] initial forced moves not implemented */
941 case VariantShogi: /* [HGM] could still mate with pawn drop */
942 case VariantKnightmate: /* [HGM] should work */
943 case VariantCylinder: /* [HGM] untested */
944 case VariantFalcon: /* [HGM] untested */
945 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
946 offboard interposition not understood */
947 case VariantNormal: /* definitely works! */
948 case VariantWildCastle: /* pieces not automatically shuffled */
949 case VariantNoCastle: /* pieces not automatically shuffled */
950 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
951 case VariantLosers: /* should work except for win condition,
952 and doesn't know captures are mandatory */
953 case VariantSuicide: /* should work except for win condition,
954 and doesn't know captures are mandatory */
955 case VariantGiveaway: /* should work except for win condition,
956 and doesn't know captures are mandatory */
957 case VariantTwoKings: /* should work */
958 case VariantAtomic: /* should work except for win condition */
959 case Variant3Check: /* should work except for win condition */
960 case VariantShatranj: /* should work except for all win conditions */
961 case VariantMakruk: /* should work except for daw countdown */
962 case VariantBerolina: /* might work if TestLegality is off */
963 case VariantCapaRandom: /* should work */
964 case VariantJanus: /* should work */
965 case VariantSuper: /* experimental */
966 case VariantGreat: /* experimental, requires legality testing to be off */
967 case VariantSChess: /* S-Chess, should work */
968 case VariantSpartan: /* should work */
973 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
974 InitEngineUCI( installDir, &second );
977 int NextIntegerFromString( char ** str, long * value )
982 while( *s == ' ' || *s == '\t' ) {
988 if( *s >= '0' && *s <= '9' ) {
989 while( *s >= '0' && *s <= '9' ) {
990 *value = *value * 10 + (*s - '0');
1002 int NextTimeControlFromString( char ** str, long * value )
1005 int result = NextIntegerFromString( str, &temp );
1008 *value = temp * 60; /* Minutes */
1009 if( **str == ':' ) {
1011 result = NextIntegerFromString( str, &temp );
1012 *value += temp; /* Seconds */
1019 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1020 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1021 int result = -1, type = 0; long temp, temp2;
1023 if(**str != ':') return -1; // old params remain in force!
1025 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1026 if( NextIntegerFromString( str, &temp ) ) return -1;
1027 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1030 /* time only: incremental or sudden-death time control */
1031 if(**str == '+') { /* increment follows; read it */
1033 if(**str == '!') type = *(*str)++; // Bronstein TC
1034 if(result = NextIntegerFromString( str, &temp2)) return -1;
1035 *inc = temp2 * 1000;
1036 if(**str == '.') { // read fraction of increment
1037 char *start = ++(*str);
1038 if(result = NextIntegerFromString( str, &temp2)) return -1;
1040 while(start++ < *str) temp2 /= 10;
1044 *moves = 0; *tc = temp * 1000; *incType = type;
1048 (*str)++; /* classical time control */
1049 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1060 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1061 { /* [HGM] get time to add from the multi-session time-control string */
1062 int incType, moves=1; /* kludge to force reading of first session */
1063 long time, increment;
1066 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1067 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1069 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1070 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1071 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1072 if(movenr == -1) return time; /* last move before new session */
1073 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1074 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1075 if(!moves) return increment; /* current session is incremental */
1076 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1077 } while(movenr >= -1); /* try again for next session */
1079 return 0; // no new time quota on this move
1083 ParseTimeControl(tc, ti, mps)
1090 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1093 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1094 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1095 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1099 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1101 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1104 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1106 snprintf(buf, MSG_SIZ, ":%s", mytc);
1108 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1110 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1115 /* Parse second time control */
1118 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1126 timeControl_2 = tc2 * 1000;
1136 timeControl = tc1 * 1000;
1139 timeIncrement = ti * 1000; /* convert to ms */
1140 movesPerSession = 0;
1143 movesPerSession = mps;
1151 if (appData.debugMode) {
1152 fprintf(debugFP, "%s\n", programVersion);
1155 set_cont_sequence(appData.wrapContSeq);
1156 if (appData.matchGames > 0) {
1157 appData.matchMode = TRUE;
1158 } else if (appData.matchMode) {
1159 appData.matchGames = 1;
1161 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1162 appData.matchGames = appData.sameColorGames;
1163 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1164 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1165 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1168 if (appData.noChessProgram || first.protocolVersion == 1) {
1171 /* kludge: allow timeout for initial "feature" commands */
1173 DisplayMessage("", _("Starting chess program"));
1174 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1179 InitBackEnd3 P((void))
1181 GameMode initialMode;
1185 InitChessProgram(&first, startedFromSetupPosition);
1187 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1188 free(programVersion);
1189 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1190 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1193 if (appData.icsActive) {
1195 /* [DM] Make a console window if needed [HGM] merged ifs */
1201 if (*appData.icsCommPort != NULLCHAR)
1202 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1203 appData.icsCommPort);
1205 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1206 appData.icsHost, appData.icsPort);
1208 if( (len > MSG_SIZ) && appData.debugMode )
1209 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1211 DisplayFatalError(buf, err, 1);
1216 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1218 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1219 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1220 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1221 } else if (appData.noChessProgram) {
1227 if (*appData.cmailGameName != NULLCHAR) {
1229 OpenLoopback(&cmailPR);
1231 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1235 DisplayMessage("", "");
1236 if (StrCaseCmp(appData.initialMode, "") == 0) {
1237 initialMode = BeginningOfGame;
1238 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1239 initialMode = TwoMachinesPlay;
1240 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1241 initialMode = AnalyzeFile;
1242 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1243 initialMode = AnalyzeMode;
1244 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1245 initialMode = MachinePlaysWhite;
1246 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1247 initialMode = MachinePlaysBlack;
1248 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1249 initialMode = EditGame;
1250 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1251 initialMode = EditPosition;
1252 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1253 initialMode = Training;
1255 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1256 if( (len > MSG_SIZ) && appData.debugMode )
1257 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1259 DisplayFatalError(buf, 0, 2);
1263 if (appData.matchMode) {
1264 /* Set up machine vs. machine match */
1265 if (appData.noChessProgram) {
1266 DisplayFatalError(_("Can't have a match with no chess programs"),
1272 if (*appData.loadGameFile != NULLCHAR) {
1273 int index = appData.loadGameIndex; // [HGM] autoinc
1274 if(index<0) lastIndex = index = 1;
1275 if (!LoadGameFromFile(appData.loadGameFile,
1277 appData.loadGameFile, FALSE)) {
1278 DisplayFatalError(_("Bad game file"), 0, 1);
1281 } else if (*appData.loadPositionFile != NULLCHAR) {
1282 int index = appData.loadPositionIndex; // [HGM] autoinc
1283 if(index<0) lastIndex = index = 1;
1284 if (!LoadPositionFromFile(appData.loadPositionFile,
1286 appData.loadPositionFile)) {
1287 DisplayFatalError(_("Bad position file"), 0, 1);
1292 } else if (*appData.cmailGameName != NULLCHAR) {
1293 /* Set up cmail mode */
1294 ReloadCmailMsgEvent(TRUE);
1296 /* Set up other modes */
1297 if (initialMode == AnalyzeFile) {
1298 if (*appData.loadGameFile == NULLCHAR) {
1299 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1303 if (*appData.loadGameFile != NULLCHAR) {
1304 (void) LoadGameFromFile(appData.loadGameFile,
1305 appData.loadGameIndex,
1306 appData.loadGameFile, TRUE);
1307 } else if (*appData.loadPositionFile != NULLCHAR) {
1308 (void) LoadPositionFromFile(appData.loadPositionFile,
1309 appData.loadPositionIndex,
1310 appData.loadPositionFile);
1311 /* [HGM] try to make self-starting even after FEN load */
1312 /* to allow automatic setup of fairy variants with wtm */
1313 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1314 gameMode = BeginningOfGame;
1315 setboardSpoiledMachineBlack = 1;
1317 /* [HGM] loadPos: make that every new game uses the setup */
1318 /* from file as long as we do not switch variant */
1319 if(!blackPlaysFirst) {
1320 startedFromPositionFile = TRUE;
1321 CopyBoard(filePosition, boards[0]);
1324 if (initialMode == AnalyzeMode) {
1325 if (appData.noChessProgram) {
1326 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1329 if (appData.icsActive) {
1330 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1334 } else if (initialMode == AnalyzeFile) {
1335 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1336 ShowThinkingEvent();
1338 AnalysisPeriodicEvent(1);
1339 } else if (initialMode == MachinePlaysWhite) {
1340 if (appData.noChessProgram) {
1341 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1345 if (appData.icsActive) {
1346 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1350 MachineWhiteEvent();
1351 } else if (initialMode == MachinePlaysBlack) {
1352 if (appData.noChessProgram) {
1353 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1357 if (appData.icsActive) {
1358 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1362 MachineBlackEvent();
1363 } else if (initialMode == TwoMachinesPlay) {
1364 if (appData.noChessProgram) {
1365 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1369 if (appData.icsActive) {
1370 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1375 } else if (initialMode == EditGame) {
1377 } else if (initialMode == EditPosition) {
1378 EditPositionEvent();
1379 } else if (initialMode == Training) {
1380 if (*appData.loadGameFile == NULLCHAR) {
1381 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1390 * Establish will establish a contact to a remote host.port.
1391 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1392 * used to talk to the host.
1393 * Returns 0 if okay, error code if not.
1400 if (*appData.icsCommPort != NULLCHAR) {
1401 /* Talk to the host through a serial comm port */
1402 return OpenCommPort(appData.icsCommPort, &icsPR);
1404 } else if (*appData.gateway != NULLCHAR) {
1405 if (*appData.remoteShell == NULLCHAR) {
1406 /* Use the rcmd protocol to run telnet program on a gateway host */
1407 snprintf(buf, sizeof(buf), "%s %s %s",
1408 appData.telnetProgram, appData.icsHost, appData.icsPort);
1409 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1412 /* Use the rsh program to run telnet program on a gateway host */
1413 if (*appData.remoteUser == NULLCHAR) {
1414 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1415 appData.gateway, appData.telnetProgram,
1416 appData.icsHost, appData.icsPort);
1418 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1419 appData.remoteShell, appData.gateway,
1420 appData.remoteUser, appData.telnetProgram,
1421 appData.icsHost, appData.icsPort);
1423 return StartChildProcess(buf, "", &icsPR);
1426 } else if (appData.useTelnet) {
1427 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1430 /* TCP socket interface differs somewhat between
1431 Unix and NT; handle details in the front end.
1433 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1437 void EscapeExpand(char *p, char *q)
1438 { // [HGM] initstring: routine to shape up string arguments
1439 while(*p++ = *q++) if(p[-1] == '\\')
1441 case 'n': p[-1] = '\n'; break;
1442 case 'r': p[-1] = '\r'; break;
1443 case 't': p[-1] = '\t'; break;
1444 case '\\': p[-1] = '\\'; break;
1445 case 0: *p = 0; return;
1446 default: p[-1] = q[-1]; break;
1451 show_bytes(fp, buf, count)
1457 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1458 fprintf(fp, "\\%03o", *buf & 0xff);
1467 /* Returns an errno value */
1469 OutputMaybeTelnet(pr, message, count, outError)
1475 char buf[8192], *p, *q, *buflim;
1476 int left, newcount, outcount;
1478 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1479 *appData.gateway != NULLCHAR) {
1480 if (appData.debugMode) {
1481 fprintf(debugFP, ">ICS: ");
1482 show_bytes(debugFP, message, count);
1483 fprintf(debugFP, "\n");
1485 return OutputToProcess(pr, message, count, outError);
1488 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1495 if (appData.debugMode) {
1496 fprintf(debugFP, ">ICS: ");
1497 show_bytes(debugFP, buf, newcount);
1498 fprintf(debugFP, "\n");
1500 outcount = OutputToProcess(pr, buf, newcount, outError);
1501 if (outcount < newcount) return -1; /* to be sure */
1508 } else if (((unsigned char) *p) == TN_IAC) {
1509 *q++ = (char) TN_IAC;
1516 if (appData.debugMode) {
1517 fprintf(debugFP, ">ICS: ");
1518 show_bytes(debugFP, buf, newcount);
1519 fprintf(debugFP, "\n");
1521 outcount = OutputToProcess(pr, buf, newcount, outError);
1522 if (outcount < newcount) return -1; /* to be sure */
1527 read_from_player(isr, closure, message, count, error)
1534 int outError, outCount;
1535 static int gotEof = 0;
1537 /* Pass data read from player on to ICS */
1540 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1541 if (outCount < count) {
1542 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1544 } else if (count < 0) {
1545 RemoveInputSource(isr);
1546 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1547 } else if (gotEof++ > 0) {
1548 RemoveInputSource(isr);
1549 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1555 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1556 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1557 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1558 SendToICS("date\n");
1559 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1562 /* added routine for printf style output to ics */
1563 void ics_printf(char *format, ...)
1565 char buffer[MSG_SIZ];
1568 va_start(args, format);
1569 vsnprintf(buffer, sizeof(buffer), format, args);
1570 buffer[sizeof(buffer)-1] = '\0';
1579 int count, outCount, outError;
1581 if (icsPR == NULL) return;
1584 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1585 if (outCount < count) {
1586 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1590 /* This is used for sending logon scripts to the ICS. Sending
1591 without a delay causes problems when using timestamp on ICC
1592 (at least on my machine). */
1594 SendToICSDelayed(s,msdelay)
1598 int count, outCount, outError;
1600 if (icsPR == NULL) return;
1603 if (appData.debugMode) {
1604 fprintf(debugFP, ">ICS: ");
1605 show_bytes(debugFP, s, count);
1606 fprintf(debugFP, "\n");
1608 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1610 if (outCount < count) {
1611 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1616 /* Remove all highlighting escape sequences in s
1617 Also deletes any suffix starting with '('
1620 StripHighlightAndTitle(s)
1623 static char retbuf[MSG_SIZ];
1626 while (*s != NULLCHAR) {
1627 while (*s == '\033') {
1628 while (*s != NULLCHAR && !isalpha(*s)) s++;
1629 if (*s != NULLCHAR) s++;
1631 while (*s != NULLCHAR && *s != '\033') {
1632 if (*s == '(' || *s == '[') {
1643 /* Remove all highlighting escape sequences in s */
1648 static char retbuf[MSG_SIZ];
1651 while (*s != NULLCHAR) {
1652 while (*s == '\033') {
1653 while (*s != NULLCHAR && !isalpha(*s)) s++;
1654 if (*s != NULLCHAR) s++;
1656 while (*s != NULLCHAR && *s != '\033') {
1664 char *variantNames[] = VARIANT_NAMES;
1669 return variantNames[v];
1673 /* Identify a variant from the strings the chess servers use or the
1674 PGN Variant tag names we use. */
1681 VariantClass v = VariantNormal;
1682 int i, found = FALSE;
1688 /* [HGM] skip over optional board-size prefixes */
1689 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1690 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1691 while( *e++ != '_');
1694 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1698 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1699 if (StrCaseStr(e, variantNames[i])) {
1700 v = (VariantClass) i;
1707 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1708 || StrCaseStr(e, "wild/fr")
1709 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1710 v = VariantFischeRandom;
1711 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1712 (i = 1, p = StrCaseStr(e, "w"))) {
1714 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1721 case 0: /* FICS only, actually */
1723 /* Castling legal even if K starts on d-file */
1724 v = VariantWildCastle;
1729 /* Castling illegal even if K & R happen to start in
1730 normal positions. */
1731 v = VariantNoCastle;
1744 /* Castling legal iff K & R start in normal positions */
1750 /* Special wilds for position setup; unclear what to do here */
1751 v = VariantLoadable;
1754 /* Bizarre ICC game */
1755 v = VariantTwoKings;
1758 v = VariantKriegspiel;
1764 v = VariantFischeRandom;
1767 v = VariantCrazyhouse;
1770 v = VariantBughouse;
1776 /* Not quite the same as FICS suicide! */
1777 v = VariantGiveaway;
1783 v = VariantShatranj;
1786 /* Temporary names for future ICC types. The name *will* change in
1787 the next xboard/WinBoard release after ICC defines it. */
1825 v = VariantCapablanca;
1828 v = VariantKnightmate;
1834 v = VariantCylinder;
1840 v = VariantCapaRandom;
1843 v = VariantBerolina;
1855 /* Found "wild" or "w" in the string but no number;
1856 must assume it's normal chess. */
1860 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1861 if( (len > MSG_SIZ) && appData.debugMode )
1862 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1864 DisplayError(buf, 0);
1870 if (appData.debugMode) {
1871 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1872 e, wnum, VariantName(v));
1877 static int leftover_start = 0, leftover_len = 0;
1878 char star_match[STAR_MATCH_N][MSG_SIZ];
1880 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1881 advance *index beyond it, and set leftover_start to the new value of
1882 *index; else return FALSE. If pattern contains the character '*', it
1883 matches any sequence of characters not containing '\r', '\n', or the
1884 character following the '*' (if any), and the matched sequence(s) are
1885 copied into star_match.
1888 looking_at(buf, index, pattern)
1893 char *bufp = &buf[*index], *patternp = pattern;
1895 char *matchp = star_match[0];
1898 if (*patternp == NULLCHAR) {
1899 *index = leftover_start = bufp - buf;
1903 if (*bufp == NULLCHAR) return FALSE;
1904 if (*patternp == '*') {
1905 if (*bufp == *(patternp + 1)) {
1907 matchp = star_match[++star_count];
1911 } else if (*bufp == '\n' || *bufp == '\r') {
1913 if (*patternp == NULLCHAR)
1918 *matchp++ = *bufp++;
1922 if (*patternp != *bufp) return FALSE;
1929 SendToPlayer(data, length)
1933 int error, outCount;
1934 outCount = OutputToProcess(NoProc, data, length, &error);
1935 if (outCount < length) {
1936 DisplayFatalError(_("Error writing to display"), error, 1);
1941 PackHolding(packed, holding)
1953 switch (runlength) {
1964 sprintf(q, "%d", runlength);
1976 /* Telnet protocol requests from the front end */
1978 TelnetRequest(ddww, option)
1979 unsigned char ddww, option;
1981 unsigned char msg[3];
1982 int outCount, outError;
1984 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1986 if (appData.debugMode) {
1987 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2003 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2012 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2015 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2020 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2022 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029 if (!appData.icsActive) return;
2030 TelnetRequest(TN_DO, TN_ECHO);
2036 if (!appData.icsActive) return;
2037 TelnetRequest(TN_DONT, TN_ECHO);
2041 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2043 /* put the holdings sent to us by the server on the board holdings area */
2044 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2048 if(gameInfo.holdingsWidth < 2) return;
2049 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2050 return; // prevent overwriting by pre-board holdings
2052 if( (int)lowestPiece >= BlackPawn ) {
2055 holdingsStartRow = BOARD_HEIGHT-1;
2058 holdingsColumn = BOARD_WIDTH-1;
2059 countsColumn = BOARD_WIDTH-2;
2060 holdingsStartRow = 0;
2064 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2065 board[i][holdingsColumn] = EmptySquare;
2066 board[i][countsColumn] = (ChessSquare) 0;
2068 while( (p=*holdings++) != NULLCHAR ) {
2069 piece = CharToPiece( ToUpper(p) );
2070 if(piece == EmptySquare) continue;
2071 /*j = (int) piece - (int) WhitePawn;*/
2072 j = PieceToNumber(piece);
2073 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2074 if(j < 0) continue; /* should not happen */
2075 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2076 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2077 board[holdingsStartRow+j*direction][countsColumn]++;
2083 VariantSwitch(Board board, VariantClass newVariant)
2085 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2086 static Board oldBoard;
2088 startedFromPositionFile = FALSE;
2089 if(gameInfo.variant == newVariant) return;
2091 /* [HGM] This routine is called each time an assignment is made to
2092 * gameInfo.variant during a game, to make sure the board sizes
2093 * are set to match the new variant. If that means adding or deleting
2094 * holdings, we shift the playing board accordingly
2095 * This kludge is needed because in ICS observe mode, we get boards
2096 * of an ongoing game without knowing the variant, and learn about the
2097 * latter only later. This can be because of the move list we requested,
2098 * in which case the game history is refilled from the beginning anyway,
2099 * but also when receiving holdings of a crazyhouse game. In the latter
2100 * case we want to add those holdings to the already received position.
2104 if (appData.debugMode) {
2105 fprintf(debugFP, "Switch board from %s to %s\n",
2106 VariantName(gameInfo.variant), VariantName(newVariant));
2107 setbuf(debugFP, NULL);
2109 shuffleOpenings = 0; /* [HGM] shuffle */
2110 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2114 newWidth = 9; newHeight = 9;
2115 gameInfo.holdingsSize = 7;
2116 case VariantBughouse:
2117 case VariantCrazyhouse:
2118 newHoldingsWidth = 2; break;
2122 newHoldingsWidth = 2;
2123 gameInfo.holdingsSize = 8;
2126 case VariantCapablanca:
2127 case VariantCapaRandom:
2130 newHoldingsWidth = gameInfo.holdingsSize = 0;
2133 if(newWidth != gameInfo.boardWidth ||
2134 newHeight != gameInfo.boardHeight ||
2135 newHoldingsWidth != gameInfo.holdingsWidth ) {
2137 /* shift position to new playing area, if needed */
2138 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2139 for(i=0; i<BOARD_HEIGHT; i++)
2140 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2141 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2143 for(i=0; i<newHeight; i++) {
2144 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2145 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2147 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2148 for(i=0; i<BOARD_HEIGHT; i++)
2149 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2150 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2153 gameInfo.boardWidth = newWidth;
2154 gameInfo.boardHeight = newHeight;
2155 gameInfo.holdingsWidth = newHoldingsWidth;
2156 gameInfo.variant = newVariant;
2157 InitDrawingSizes(-2, 0);
2158 } else gameInfo.variant = newVariant;
2159 CopyBoard(oldBoard, board); // remember correctly formatted board
2160 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2161 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2164 static int loggedOn = FALSE;
2166 /*-- Game start info cache: --*/
2168 char gs_kind[MSG_SIZ];
2169 static char player1Name[128] = "";
2170 static char player2Name[128] = "";
2171 static char cont_seq[] = "\n\\ ";
2172 static int player1Rating = -1;
2173 static int player2Rating = -1;
2174 /*----------------------------*/
2176 ColorClass curColor = ColorNormal;
2177 int suppressKibitz = 0;
2180 Boolean soughtPending = FALSE;
2181 Boolean seekGraphUp;
2182 #define MAX_SEEK_ADS 200
2184 char *seekAdList[MAX_SEEK_ADS];
2185 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2186 float tcList[MAX_SEEK_ADS];
2187 char colorList[MAX_SEEK_ADS];
2188 int nrOfSeekAds = 0;
2189 int minRating = 1010, maxRating = 2800;
2190 int hMargin = 10, vMargin = 20, h, w;
2191 extern int squareSize, lineGap;
2196 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2197 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2198 if(r < minRating+100 && r >=0 ) r = minRating+100;
2199 if(r > maxRating) r = maxRating;
2200 if(tc < 1.) tc = 1.;
2201 if(tc > 95.) tc = 95.;
2202 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2203 y = ((double)r - minRating)/(maxRating - minRating)
2204 * (h-vMargin-squareSize/8-1) + vMargin;
2205 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2206 if(strstr(seekAdList[i], " u ")) color = 1;
2207 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2208 !strstr(seekAdList[i], "bullet") &&
2209 !strstr(seekAdList[i], "blitz") &&
2210 !strstr(seekAdList[i], "standard") ) color = 2;
2211 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2212 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2216 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2218 char buf[MSG_SIZ], *ext = "";
2219 VariantClass v = StringToVariant(type);
2220 if(strstr(type, "wild")) {
2221 ext = type + 4; // append wild number
2222 if(v == VariantFischeRandom) type = "chess960"; else
2223 if(v == VariantLoadable) type = "setup"; else
2224 type = VariantName(v);
2226 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2227 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2228 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2229 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2230 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2231 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2232 seekNrList[nrOfSeekAds] = nr;
2233 zList[nrOfSeekAds] = 0;
2234 seekAdList[nrOfSeekAds++] = StrSave(buf);
2235 if(plot) PlotSeekAd(nrOfSeekAds-1);
2242 int x = xList[i], y = yList[i], d=squareSize/4, k;
2243 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2244 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2245 // now replot every dot that overlapped
2246 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2247 int xx = xList[k], yy = yList[k];
2248 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2249 DrawSeekDot(xx, yy, colorList[k]);
2254 RemoveSeekAd(int nr)
2257 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2259 if(seekAdList[i]) free(seekAdList[i]);
2260 seekAdList[i] = seekAdList[--nrOfSeekAds];
2261 seekNrList[i] = seekNrList[nrOfSeekAds];
2262 ratingList[i] = ratingList[nrOfSeekAds];
2263 colorList[i] = colorList[nrOfSeekAds];
2264 tcList[i] = tcList[nrOfSeekAds];
2265 xList[i] = xList[nrOfSeekAds];
2266 yList[i] = yList[nrOfSeekAds];
2267 zList[i] = zList[nrOfSeekAds];
2268 seekAdList[nrOfSeekAds] = NULL;
2274 MatchSoughtLine(char *line)
2276 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2277 int nr, base, inc, u=0; char dummy;
2279 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2282 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2283 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2284 // match: compact and save the line
2285 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2295 if(!seekGraphUp) return FALSE;
2296 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2297 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2299 DrawSeekBackground(0, 0, w, h);
2300 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2301 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2302 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2303 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2305 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2308 snprintf(buf, MSG_SIZ, "%d", i);
2309 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2312 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2313 for(i=1; i<100; i+=(i<10?1:5)) {
2314 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2315 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2316 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2318 snprintf(buf, MSG_SIZ, "%d", i);
2319 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2322 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2326 int SeekGraphClick(ClickType click, int x, int y, int moving)
2328 static int lastDown = 0, displayed = 0, lastSecond;
2329 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2330 if(click == Release || moving) return FALSE;
2332 soughtPending = TRUE;
2333 SendToICS(ics_prefix);
2334 SendToICS("sought\n"); // should this be "sought all"?
2335 } else { // issue challenge based on clicked ad
2336 int dist = 10000; int i, closest = 0, second = 0;
2337 for(i=0; i<nrOfSeekAds; i++) {
2338 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2339 if(d < dist) { dist = d; closest = i; }
2340 second += (d - zList[i] < 120); // count in-range ads
2341 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2345 second = (second > 1);
2346 if(displayed != closest || second != lastSecond) {
2347 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2348 lastSecond = second; displayed = closest;
2350 if(click == Press) {
2351 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2354 } // on press 'hit', only show info
2355 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2356 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2357 SendToICS(ics_prefix);
2359 return TRUE; // let incoming board of started game pop down the graph
2360 } else if(click == Release) { // release 'miss' is ignored
2361 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2362 if(moving == 2) { // right up-click
2363 nrOfSeekAds = 0; // refresh graph
2364 soughtPending = TRUE;
2365 SendToICS(ics_prefix);
2366 SendToICS("sought\n"); // should this be "sought all"?
2369 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2370 // press miss or release hit 'pop down' seek graph
2371 seekGraphUp = FALSE;
2372 DrawPosition(TRUE, NULL);
2378 read_from_ics(isr, closure, data, count, error)
2385 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2386 #define STARTED_NONE 0
2387 #define STARTED_MOVES 1
2388 #define STARTED_BOARD 2
2389 #define STARTED_OBSERVE 3
2390 #define STARTED_HOLDINGS 4
2391 #define STARTED_CHATTER 5
2392 #define STARTED_COMMENT 6
2393 #define STARTED_MOVES_NOHIDE 7
2395 static int started = STARTED_NONE;
2396 static char parse[20000];
2397 static int parse_pos = 0;
2398 static char buf[BUF_SIZE + 1];
2399 static int firstTime = TRUE, intfSet = FALSE;
2400 static ColorClass prevColor = ColorNormal;
2401 static int savingComment = FALSE;
2402 static int cmatch = 0; // continuation sequence match
2409 int backup; /* [DM] For zippy color lines */
2411 char talker[MSG_SIZ]; // [HGM] chat
2414 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2416 if (appData.debugMode) {
2418 fprintf(debugFP, "<ICS: ");
2419 show_bytes(debugFP, data, count);
2420 fprintf(debugFP, "\n");
2424 if (appData.debugMode) { int f = forwardMostMove;
2425 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2426 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2427 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2430 /* If last read ended with a partial line that we couldn't parse,
2431 prepend it to the new read and try again. */
2432 if (leftover_len > 0) {
2433 for (i=0; i<leftover_len; i++)
2434 buf[i] = buf[leftover_start + i];
2437 /* copy new characters into the buffer */
2438 bp = buf + leftover_len;
2439 buf_len=leftover_len;
2440 for (i=0; i<count; i++)
2443 if (data[i] == '\r')
2446 // join lines split by ICS?
2447 if (!appData.noJoin)
2450 Joining just consists of finding matches against the
2451 continuation sequence, and discarding that sequence
2452 if found instead of copying it. So, until a match
2453 fails, there's nothing to do since it might be the
2454 complete sequence, and thus, something we don't want
2457 if (data[i] == cont_seq[cmatch])
2460 if (cmatch == strlen(cont_seq))
2462 cmatch = 0; // complete match. just reset the counter
2465 it's possible for the ICS to not include the space
2466 at the end of the last word, making our [correct]
2467 join operation fuse two separate words. the server
2468 does this when the space occurs at the width setting.
2470 if (!buf_len || buf[buf_len-1] != ' ')
2481 match failed, so we have to copy what matched before
2482 falling through and copying this character. In reality,
2483 this will only ever be just the newline character, but
2484 it doesn't hurt to be precise.
2486 strncpy(bp, cont_seq, cmatch);
2498 buf[buf_len] = NULLCHAR;
2499 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2504 while (i < buf_len) {
2505 /* Deal with part of the TELNET option negotiation
2506 protocol. We refuse to do anything beyond the
2507 defaults, except that we allow the WILL ECHO option,
2508 which ICS uses to turn off password echoing when we are
2509 directly connected to it. We reject this option
2510 if localLineEditing mode is on (always on in xboard)
2511 and we are talking to port 23, which might be a real
2512 telnet server that will try to keep WILL ECHO on permanently.
2514 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2515 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2516 unsigned char option;
2518 switch ((unsigned char) buf[++i]) {
2520 if (appData.debugMode)
2521 fprintf(debugFP, "\n<WILL ");
2522 switch (option = (unsigned char) buf[++i]) {
2524 if (appData.debugMode)
2525 fprintf(debugFP, "ECHO ");
2526 /* Reply only if this is a change, according
2527 to the protocol rules. */
2528 if (remoteEchoOption) break;
2529 if (appData.localLineEditing &&
2530 atoi(appData.icsPort) == TN_PORT) {
2531 TelnetRequest(TN_DONT, TN_ECHO);
2534 TelnetRequest(TN_DO, TN_ECHO);
2535 remoteEchoOption = TRUE;
2539 if (appData.debugMode)
2540 fprintf(debugFP, "%d ", option);
2541 /* Whatever this is, we don't want it. */
2542 TelnetRequest(TN_DONT, option);
2547 if (appData.debugMode)
2548 fprintf(debugFP, "\n<WONT ");
2549 switch (option = (unsigned char) buf[++i]) {
2551 if (appData.debugMode)
2552 fprintf(debugFP, "ECHO ");
2553 /* Reply only if this is a change, according
2554 to the protocol rules. */
2555 if (!remoteEchoOption) break;
2557 TelnetRequest(TN_DONT, TN_ECHO);
2558 remoteEchoOption = FALSE;
2561 if (appData.debugMode)
2562 fprintf(debugFP, "%d ", (unsigned char) option);
2563 /* Whatever this is, it must already be turned
2564 off, because we never agree to turn on
2565 anything non-default, so according to the
2566 protocol rules, we don't reply. */
2571 if (appData.debugMode)
2572 fprintf(debugFP, "\n<DO ");
2573 switch (option = (unsigned char) buf[++i]) {
2575 /* Whatever this is, we refuse to do it. */
2576 if (appData.debugMode)
2577 fprintf(debugFP, "%d ", option);
2578 TelnetRequest(TN_WONT, option);
2583 if (appData.debugMode)
2584 fprintf(debugFP, "\n<DONT ");
2585 switch (option = (unsigned char) buf[++i]) {
2587 if (appData.debugMode)
2588 fprintf(debugFP, "%d ", option);
2589 /* Whatever this is, we are already not doing
2590 it, because we never agree to do anything
2591 non-default, so according to the protocol
2592 rules, we don't reply. */
2597 if (appData.debugMode)
2598 fprintf(debugFP, "\n<IAC ");
2599 /* Doubled IAC; pass it through */
2603 if (appData.debugMode)
2604 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2605 /* Drop all other telnet commands on the floor */
2608 if (oldi > next_out)
2609 SendToPlayer(&buf[next_out], oldi - next_out);
2615 /* OK, this at least will *usually* work */
2616 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2620 if (loggedOn && !intfSet) {
2621 if (ics_type == ICS_ICC) {
2622 snprintf(str, MSG_SIZ,
2623 "/set-quietly interface %s\n/set-quietly style 12\n",
2625 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2626 strcat(str, "/set-2 51 1\n/set seek 1\n");
2627 } else if (ics_type == ICS_CHESSNET) {
2628 snprintf(str, MSG_SIZ, "/style 12\n");
2630 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2631 strcat(str, programVersion);
2632 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2633 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2634 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2636 strcat(str, "$iset nohighlight 1\n");
2638 strcat(str, "$iset lock 1\n$style 12\n");
2641 NotifyFrontendLogin();
2645 if (started == STARTED_COMMENT) {
2646 /* Accumulate characters in comment */
2647 parse[parse_pos++] = buf[i];
2648 if (buf[i] == '\n') {
2649 parse[parse_pos] = NULLCHAR;
2650 if(chattingPartner>=0) {
2652 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2653 OutputChatMessage(chattingPartner, mess);
2654 chattingPartner = -1;
2655 next_out = i+1; // [HGM] suppress printing in ICS window
2657 if(!suppressKibitz) // [HGM] kibitz
2658 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2659 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2660 int nrDigit = 0, nrAlph = 0, j;
2661 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2662 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2663 parse[parse_pos] = NULLCHAR;
2664 // try to be smart: if it does not look like search info, it should go to
2665 // ICS interaction window after all, not to engine-output window.
2666 for(j=0; j<parse_pos; j++) { // count letters and digits
2667 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2668 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2669 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2671 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2672 int depth=0; float score;
2673 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2674 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2675 pvInfoList[forwardMostMove-1].depth = depth;
2676 pvInfoList[forwardMostMove-1].score = 100*score;
2678 OutputKibitz(suppressKibitz, parse);
2681 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2682 SendToPlayer(tmp, strlen(tmp));
2684 next_out = i+1; // [HGM] suppress printing in ICS window
2686 started = STARTED_NONE;
2688 /* Don't match patterns against characters in comment */
2693 if (started == STARTED_CHATTER) {
2694 if (buf[i] != '\n') {
2695 /* Don't match patterns against characters in chatter */
2699 started = STARTED_NONE;
2700 if(suppressKibitz) next_out = i+1;
2703 /* Kludge to deal with rcmd protocol */
2704 if (firstTime && looking_at(buf, &i, "\001*")) {
2705 DisplayFatalError(&buf[1], 0, 1);
2711 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2714 if (appData.debugMode)
2715 fprintf(debugFP, "ics_type %d\n", ics_type);
2718 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2719 ics_type = ICS_FICS;
2721 if (appData.debugMode)
2722 fprintf(debugFP, "ics_type %d\n", ics_type);
2725 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2726 ics_type = ICS_CHESSNET;
2728 if (appData.debugMode)
2729 fprintf(debugFP, "ics_type %d\n", ics_type);
2734 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2735 looking_at(buf, &i, "Logging you in as \"*\"") ||
2736 looking_at(buf, &i, "will be \"*\""))) {
2737 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2741 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2743 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2744 DisplayIcsInteractionTitle(buf);
2745 have_set_title = TRUE;
2748 /* skip finger notes */
2749 if (started == STARTED_NONE &&
2750 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2751 (buf[i] == '1' && buf[i+1] == '0')) &&
2752 buf[i+2] == ':' && buf[i+3] == ' ') {
2753 started = STARTED_CHATTER;
2759 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2760 if(appData.seekGraph) {
2761 if(soughtPending && MatchSoughtLine(buf+i)) {
2762 i = strstr(buf+i, "rated") - buf;
2763 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2764 next_out = leftover_start = i;
2765 started = STARTED_CHATTER;
2766 suppressKibitz = TRUE;
2769 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2770 && looking_at(buf, &i, "* ads displayed")) {
2771 soughtPending = FALSE;
2776 if(appData.autoRefresh) {
2777 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2778 int s = (ics_type == ICS_ICC); // ICC format differs
2780 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2781 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2782 looking_at(buf, &i, "*% "); // eat prompt
2783 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2784 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2785 next_out = i; // suppress
2788 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2789 char *p = star_match[0];
2791 if(seekGraphUp) RemoveSeekAd(atoi(p));
2792 while(*p && *p++ != ' '); // next
2794 looking_at(buf, &i, "*% "); // eat prompt
2795 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2802 /* skip formula vars */
2803 if (started == STARTED_NONE &&
2804 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2805 started = STARTED_CHATTER;
2810 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2811 if (appData.autoKibitz && started == STARTED_NONE &&
2812 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2813 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2814 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2815 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2816 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2817 suppressKibitz = TRUE;
2818 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2820 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2821 && (gameMode == IcsPlayingWhite)) ||
2822 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2823 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2824 started = STARTED_CHATTER; // own kibitz we simply discard
2826 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2827 parse_pos = 0; parse[0] = NULLCHAR;
2828 savingComment = TRUE;
2829 suppressKibitz = gameMode != IcsObserving ? 2 :
2830 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2834 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2835 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2836 && atoi(star_match[0])) {
2837 // suppress the acknowledgements of our own autoKibitz
2839 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2840 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2841 SendToPlayer(star_match[0], strlen(star_match[0]));
2842 if(looking_at(buf, &i, "*% ")) // eat prompt
2843 suppressKibitz = FALSE;
2847 } // [HGM] kibitz: end of patch
2849 // [HGM] chat: intercept tells by users for which we have an open chat window
2851 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2852 looking_at(buf, &i, "* whispers:") ||
2853 looking_at(buf, &i, "* kibitzes:") ||
2854 looking_at(buf, &i, "* shouts:") ||
2855 looking_at(buf, &i, "* c-shouts:") ||
2856 looking_at(buf, &i, "--> * ") ||
2857 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2858 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2859 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2860 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2862 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2863 chattingPartner = -1;
2865 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2866 for(p=0; p<MAX_CHAT; p++) {
2867 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2868 talker[0] = '['; strcat(talker, "] ");
2869 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2870 chattingPartner = p; break;
2873 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2874 for(p=0; p<MAX_CHAT; p++) {
2875 if(!strcmp("kibitzes", chatPartner[p])) {
2876 talker[0] = '['; strcat(talker, "] ");
2877 chattingPartner = p; break;
2880 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2881 for(p=0; p<MAX_CHAT; p++) {
2882 if(!strcmp("whispers", chatPartner[p])) {
2883 talker[0] = '['; strcat(talker, "] ");
2884 chattingPartner = p; break;
2887 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2888 if(buf[i-8] == '-' && buf[i-3] == 't')
2889 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2890 if(!strcmp("c-shouts", chatPartner[p])) {
2891 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2892 chattingPartner = p; break;
2895 if(chattingPartner < 0)
2896 for(p=0; p<MAX_CHAT; p++) {
2897 if(!strcmp("shouts", chatPartner[p])) {
2898 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2899 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2900 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2901 chattingPartner = p; break;
2905 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2906 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2907 talker[0] = 0; Colorize(ColorTell, FALSE);
2908 chattingPartner = p; break;
2910 if(chattingPartner<0) i = oldi; else {
2911 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2912 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2913 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2914 started = STARTED_COMMENT;
2915 parse_pos = 0; parse[0] = NULLCHAR;
2916 savingComment = 3 + chattingPartner; // counts as TRUE
2917 suppressKibitz = TRUE;
2920 } // [HGM] chat: end of patch
2922 if (appData.zippyTalk || appData.zippyPlay) {
2923 /* [DM] Backup address for color zippy lines */
2926 if (loggedOn == TRUE)
2927 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2928 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2930 } // [DM] 'else { ' deleted
2932 /* Regular tells and says */
2933 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2934 looking_at(buf, &i, "* (your partner) tells you: ") ||
2935 looking_at(buf, &i, "* says: ") ||
2936 /* Don't color "message" or "messages" output */
2937 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2938 looking_at(buf, &i, "*. * at *:*: ") ||
2939 looking_at(buf, &i, "--* (*:*): ") ||
2940 /* Message notifications (same color as tells) */
2941 looking_at(buf, &i, "* has left a message ") ||
2942 looking_at(buf, &i, "* just sent you a message:\n") ||
2943 /* Whispers and kibitzes */
2944 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2945 looking_at(buf, &i, "* kibitzes: ") ||
2947 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2949 if (tkind == 1 && strchr(star_match[0], ':')) {
2950 /* Avoid "tells you:" spoofs in channels */
2953 if (star_match[0][0] == NULLCHAR ||
2954 strchr(star_match[0], ' ') ||
2955 (tkind == 3 && strchr(star_match[1], ' '))) {
2956 /* Reject bogus matches */
2959 if (appData.colorize) {
2960 if (oldi > next_out) {
2961 SendToPlayer(&buf[next_out], oldi - next_out);
2966 Colorize(ColorTell, FALSE);
2967 curColor = ColorTell;
2970 Colorize(ColorKibitz, FALSE);
2971 curColor = ColorKibitz;
2974 p = strrchr(star_match[1], '(');
2981 Colorize(ColorChannel1, FALSE);
2982 curColor = ColorChannel1;
2984 Colorize(ColorChannel, FALSE);
2985 curColor = ColorChannel;
2989 curColor = ColorNormal;
2993 if (started == STARTED_NONE && appData.autoComment &&
2994 (gameMode == IcsObserving ||
2995 gameMode == IcsPlayingWhite ||
2996 gameMode == IcsPlayingBlack)) {
2997 parse_pos = i - oldi;
2998 memcpy(parse, &buf[oldi], parse_pos);
2999 parse[parse_pos] = NULLCHAR;
3000 started = STARTED_COMMENT;
3001 savingComment = TRUE;
3003 started = STARTED_CHATTER;
3004 savingComment = FALSE;
3011 if (looking_at(buf, &i, "* s-shouts: ") ||
3012 looking_at(buf, &i, "* c-shouts: ")) {
3013 if (appData.colorize) {
3014 if (oldi > next_out) {
3015 SendToPlayer(&buf[next_out], oldi - next_out);
3018 Colorize(ColorSShout, FALSE);
3019 curColor = ColorSShout;
3022 started = STARTED_CHATTER;
3026 if (looking_at(buf, &i, "--->")) {
3031 if (looking_at(buf, &i, "* shouts: ") ||
3032 looking_at(buf, &i, "--> ")) {
3033 if (appData.colorize) {
3034 if (oldi > next_out) {
3035 SendToPlayer(&buf[next_out], oldi - next_out);
3038 Colorize(ColorShout, FALSE);
3039 curColor = ColorShout;
3042 started = STARTED_CHATTER;
3046 if (looking_at( buf, &i, "Challenge:")) {
3047 if (appData.colorize) {
3048 if (oldi > next_out) {
3049 SendToPlayer(&buf[next_out], oldi - next_out);
3052 Colorize(ColorChallenge, FALSE);
3053 curColor = ColorChallenge;
3059 if (looking_at(buf, &i, "* offers you") ||
3060 looking_at(buf, &i, "* offers to be") ||
3061 looking_at(buf, &i, "* would like to") ||
3062 looking_at(buf, &i, "* requests to") ||
3063 looking_at(buf, &i, "Your opponent offers") ||
3064 looking_at(buf, &i, "Your opponent requests")) {
3066 if (appData.colorize) {
3067 if (oldi > next_out) {
3068 SendToPlayer(&buf[next_out], oldi - next_out);
3071 Colorize(ColorRequest, FALSE);
3072 curColor = ColorRequest;
3077 if (looking_at(buf, &i, "* (*) seeking")) {
3078 if (appData.colorize) {
3079 if (oldi > next_out) {
3080 SendToPlayer(&buf[next_out], oldi - next_out);
3083 Colorize(ColorSeek, FALSE);
3084 curColor = ColorSeek;
3089 if (looking_at(buf, &i, "\\ ")) {
3090 if (prevColor != ColorNormal) {
3091 if (oldi > next_out) {
3092 SendToPlayer(&buf[next_out], oldi - next_out);
3095 Colorize(prevColor, TRUE);
3096 curColor = prevColor;
3098 if (savingComment) {
3099 parse_pos = i - oldi;
3100 memcpy(parse, &buf[oldi], parse_pos);
3101 parse[parse_pos] = NULLCHAR;
3102 started = STARTED_COMMENT;
3103 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3104 chattingPartner = savingComment - 3; // kludge to remember the box
3106 started = STARTED_CHATTER;
3111 if (looking_at(buf, &i, "Black Strength :") ||
3112 looking_at(buf, &i, "<<< style 10 board >>>") ||
3113 looking_at(buf, &i, "<10>") ||
3114 looking_at(buf, &i, "#@#")) {
3115 /* Wrong board style */
3117 SendToICS(ics_prefix);
3118 SendToICS("set style 12\n");
3119 SendToICS(ics_prefix);
3120 SendToICS("refresh\n");
3124 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3126 have_sent_ICS_logon = 1;
3130 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3131 (looking_at(buf, &i, "\n<12> ") ||
3132 looking_at(buf, &i, "<12> "))) {
3134 if (oldi > next_out) {
3135 SendToPlayer(&buf[next_out], oldi - next_out);
3138 started = STARTED_BOARD;
3143 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3144 looking_at(buf, &i, "<b1> ")) {
3145 if (oldi > next_out) {
3146 SendToPlayer(&buf[next_out], oldi - next_out);
3149 started = STARTED_HOLDINGS;
3154 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3156 /* Header for a move list -- first line */
3158 switch (ics_getting_history) {
3162 case BeginningOfGame:
3163 /* User typed "moves" or "oldmoves" while we
3164 were idle. Pretend we asked for these
3165 moves and soak them up so user can step
3166 through them and/or save them.
3169 gameMode = IcsObserving;
3172 ics_getting_history = H_GOT_UNREQ_HEADER;
3174 case EditGame: /*?*/
3175 case EditPosition: /*?*/
3176 /* Should above feature work in these modes too? */
3177 /* For now it doesn't */
3178 ics_getting_history = H_GOT_UNWANTED_HEADER;
3181 ics_getting_history = H_GOT_UNWANTED_HEADER;
3186 /* Is this the right one? */
3187 if (gameInfo.white && gameInfo.black &&
3188 strcmp(gameInfo.white, star_match[0]) == 0 &&
3189 strcmp(gameInfo.black, star_match[2]) == 0) {
3191 ics_getting_history = H_GOT_REQ_HEADER;
3194 case H_GOT_REQ_HEADER:
3195 case H_GOT_UNREQ_HEADER:
3196 case H_GOT_UNWANTED_HEADER:
3197 case H_GETTING_MOVES:
3198 /* Should not happen */
3199 DisplayError(_("Error gathering move list: two headers"), 0);
3200 ics_getting_history = H_FALSE;
3204 /* Save player ratings into gameInfo if needed */
3205 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3206 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3207 (gameInfo.whiteRating == -1 ||
3208 gameInfo.blackRating == -1)) {
3210 gameInfo.whiteRating = string_to_rating(star_match[1]);
3211 gameInfo.blackRating = string_to_rating(star_match[3]);
3212 if (appData.debugMode)
3213 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3214 gameInfo.whiteRating, gameInfo.blackRating);
3219 if (looking_at(buf, &i,
3220 "* * match, initial time: * minute*, increment: * second")) {
3221 /* Header for a move list -- second line */
3222 /* Initial board will follow if this is a wild game */
3223 if (gameInfo.event != NULL) free(gameInfo.event);
3224 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3225 gameInfo.event = StrSave(str);
3226 /* [HGM] we switched variant. Translate boards if needed. */
3227 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3231 if (looking_at(buf, &i, "Move ")) {
3232 /* Beginning of a move list */
3233 switch (ics_getting_history) {
3235 /* Normally should not happen */
3236 /* Maybe user hit reset while we were parsing */
3239 /* Happens if we are ignoring a move list that is not
3240 * the one we just requested. Common if the user
3241 * tries to observe two games without turning off
3244 case H_GETTING_MOVES:
3245 /* Should not happen */
3246 DisplayError(_("Error gathering move list: nested"), 0);
3247 ics_getting_history = H_FALSE;
3249 case H_GOT_REQ_HEADER:
3250 ics_getting_history = H_GETTING_MOVES;
3251 started = STARTED_MOVES;
3253 if (oldi > next_out) {
3254 SendToPlayer(&buf[next_out], oldi - next_out);
3257 case H_GOT_UNREQ_HEADER:
3258 ics_getting_history = H_GETTING_MOVES;
3259 started = STARTED_MOVES_NOHIDE;
3262 case H_GOT_UNWANTED_HEADER:
3263 ics_getting_history = H_FALSE;
3269 if (looking_at(buf, &i, "% ") ||
3270 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3271 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3272 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3273 soughtPending = FALSE;
3277 if(suppressKibitz) next_out = i;
3278 savingComment = FALSE;
3282 case STARTED_MOVES_NOHIDE:
3283 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3284 parse[parse_pos + i - oldi] = NULLCHAR;
3285 ParseGameHistory(parse);
3287 if (appData.zippyPlay && first.initDone) {
3288 FeedMovesToProgram(&first, forwardMostMove);
3289 if (gameMode == IcsPlayingWhite) {
3290 if (WhiteOnMove(forwardMostMove)) {
3291 if (first.sendTime) {
3292 if (first.useColors) {
3293 SendToProgram("black\n", &first);
3295 SendTimeRemaining(&first, TRUE);
3297 if (first.useColors) {
3298 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3300 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3301 first.maybeThinking = TRUE;
3303 if (first.usePlayother) {
3304 if (first.sendTime) {
3305 SendTimeRemaining(&first, TRUE);
3307 SendToProgram("playother\n", &first);
3313 } else if (gameMode == IcsPlayingBlack) {
3314 if (!WhiteOnMove(forwardMostMove)) {
3315 if (first.sendTime) {
3316 if (first.useColors) {
3317 SendToProgram("white\n", &first);
3319 SendTimeRemaining(&first, FALSE);
3321 if (first.useColors) {
3322 SendToProgram("black\n", &first);
3324 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3325 first.maybeThinking = TRUE;
3327 if (first.usePlayother) {
3328 if (first.sendTime) {
3329 SendTimeRemaining(&first, FALSE);
3331 SendToProgram("playother\n", &first);
3340 if (gameMode == IcsObserving && ics_gamenum == -1) {
3341 /* Moves came from oldmoves or moves command
3342 while we weren't doing anything else.
3344 currentMove = forwardMostMove;
3345 ClearHighlights();/*!!could figure this out*/
3346 flipView = appData.flipView;
3347 DrawPosition(TRUE, boards[currentMove]);
3348 DisplayBothClocks();
3349 snprintf(str, MSG_SIZ, "%s vs. %s",
3350 gameInfo.white, gameInfo.black);
3354 /* Moves were history of an active game&nb