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 MatchEvent(int mode)
1180 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1181 /* Set up machine vs. machine match */
1182 if (appData.noChessProgram) {
1183 DisplayFatalError(_("Can't have a match with no chess programs"),
1189 if (*appData.loadGameFile != NULLCHAR) {
1190 int index = appData.loadGameIndex; // [HGM] autoinc
1191 if(index<0) lastIndex = index = 1;
1192 if (!LoadGameFromFile(appData.loadGameFile,
1194 appData.loadGameFile, FALSE)) {
1195 DisplayFatalError(_("Bad game file"), 0, 1);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 int index = appData.loadPositionIndex; // [HGM] autoinc
1200 if(index<0) lastIndex = index = 1;
1201 if (!LoadPositionFromFile(appData.loadPositionFile,
1203 appData.loadPositionFile)) {
1204 DisplayFatalError(_("Bad position file"), 0, 1);
1208 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1213 InitBackEnd3 P((void))
1215 GameMode initialMode;
1219 InitChessProgram(&first, startedFromSetupPosition);
1221 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1222 free(programVersion);
1223 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1224 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1227 if (appData.icsActive) {
1229 /* [DM] Make a console window if needed [HGM] merged ifs */
1235 if (*appData.icsCommPort != NULLCHAR)
1236 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1237 appData.icsCommPort);
1239 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1240 appData.icsHost, appData.icsPort);
1242 if( (len > MSG_SIZ) && appData.debugMode )
1243 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1245 DisplayFatalError(buf, err, 1);
1250 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1252 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1253 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1254 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1255 } else if (appData.noChessProgram) {
1261 if (*appData.cmailGameName != NULLCHAR) {
1263 OpenLoopback(&cmailPR);
1265 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1269 DisplayMessage("", "");
1270 if (StrCaseCmp(appData.initialMode, "") == 0) {
1271 initialMode = BeginningOfGame;
1272 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1273 initialMode = TwoMachinesPlay;
1274 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1275 initialMode = AnalyzeFile;
1276 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1277 initialMode = AnalyzeMode;
1278 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1279 initialMode = MachinePlaysWhite;
1280 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1281 initialMode = MachinePlaysBlack;
1282 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1283 initialMode = EditGame;
1284 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1285 initialMode = EditPosition;
1286 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1287 initialMode = Training;
1289 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1290 if( (len > MSG_SIZ) && appData.debugMode )
1291 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1293 DisplayFatalError(buf, 0, 2);
1297 if (appData.matchMode) {
1299 } else if (*appData.cmailGameName != NULLCHAR) {
1300 /* Set up cmail mode */
1301 ReloadCmailMsgEvent(TRUE);
1303 /* Set up other modes */
1304 if (initialMode == AnalyzeFile) {
1305 if (*appData.loadGameFile == NULLCHAR) {
1306 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1310 if (*appData.loadGameFile != NULLCHAR) {
1311 (void) LoadGameFromFile(appData.loadGameFile,
1312 appData.loadGameIndex,
1313 appData.loadGameFile, TRUE);
1314 } else if (*appData.loadPositionFile != NULLCHAR) {
1315 (void) LoadPositionFromFile(appData.loadPositionFile,
1316 appData.loadPositionIndex,
1317 appData.loadPositionFile);
1318 /* [HGM] try to make self-starting even after FEN load */
1319 /* to allow automatic setup of fairy variants with wtm */
1320 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1321 gameMode = BeginningOfGame;
1322 setboardSpoiledMachineBlack = 1;
1324 /* [HGM] loadPos: make that every new game uses the setup */
1325 /* from file as long as we do not switch variant */
1326 if(!blackPlaysFirst) {
1327 startedFromPositionFile = TRUE;
1328 CopyBoard(filePosition, boards[0]);
1331 if (initialMode == AnalyzeMode) {
1332 if (appData.noChessProgram) {
1333 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1336 if (appData.icsActive) {
1337 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1341 } else if (initialMode == AnalyzeFile) {
1342 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1343 ShowThinkingEvent();
1345 AnalysisPeriodicEvent(1);
1346 } else if (initialMode == MachinePlaysWhite) {
1347 if (appData.noChessProgram) {
1348 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1352 if (appData.icsActive) {
1353 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1357 MachineWhiteEvent();
1358 } else if (initialMode == MachinePlaysBlack) {
1359 if (appData.noChessProgram) {
1360 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1364 if (appData.icsActive) {
1365 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1369 MachineBlackEvent();
1370 } else if (initialMode == TwoMachinesPlay) {
1371 if (appData.noChessProgram) {
1372 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1376 if (appData.icsActive) {
1377 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1382 } else if (initialMode == EditGame) {
1384 } else if (initialMode == EditPosition) {
1385 EditPositionEvent();
1386 } else if (initialMode == Training) {
1387 if (*appData.loadGameFile == NULLCHAR) {
1388 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1397 * Establish will establish a contact to a remote host.port.
1398 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1399 * used to talk to the host.
1400 * Returns 0 if okay, error code if not.
1407 if (*appData.icsCommPort != NULLCHAR) {
1408 /* Talk to the host through a serial comm port */
1409 return OpenCommPort(appData.icsCommPort, &icsPR);
1411 } else if (*appData.gateway != NULLCHAR) {
1412 if (*appData.remoteShell == NULLCHAR) {
1413 /* Use the rcmd protocol to run telnet program on a gateway host */
1414 snprintf(buf, sizeof(buf), "%s %s %s",
1415 appData.telnetProgram, appData.icsHost, appData.icsPort);
1416 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1419 /* Use the rsh program to run telnet program on a gateway host */
1420 if (*appData.remoteUser == NULLCHAR) {
1421 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1422 appData.gateway, appData.telnetProgram,
1423 appData.icsHost, appData.icsPort);
1425 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1426 appData.remoteShell, appData.gateway,
1427 appData.remoteUser, appData.telnetProgram,
1428 appData.icsHost, appData.icsPort);
1430 return StartChildProcess(buf, "", &icsPR);
1433 } else if (appData.useTelnet) {
1434 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1437 /* TCP socket interface differs somewhat between
1438 Unix and NT; handle details in the front end.
1440 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1444 void EscapeExpand(char *p, char *q)
1445 { // [HGM] initstring: routine to shape up string arguments
1446 while(*p++ = *q++) if(p[-1] == '\\')
1448 case 'n': p[-1] = '\n'; break;
1449 case 'r': p[-1] = '\r'; break;
1450 case 't': p[-1] = '\t'; break;
1451 case '\\': p[-1] = '\\'; break;
1452 case 0: *p = 0; return;
1453 default: p[-1] = q[-1]; break;
1458 show_bytes(fp, buf, count)
1464 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1465 fprintf(fp, "\\%03o", *buf & 0xff);
1474 /* Returns an errno value */
1476 OutputMaybeTelnet(pr, message, count, outError)
1482 char buf[8192], *p, *q, *buflim;
1483 int left, newcount, outcount;
1485 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1486 *appData.gateway != NULLCHAR) {
1487 if (appData.debugMode) {
1488 fprintf(debugFP, ">ICS: ");
1489 show_bytes(debugFP, message, count);
1490 fprintf(debugFP, "\n");
1492 return OutputToProcess(pr, message, count, outError);
1495 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1502 if (appData.debugMode) {
1503 fprintf(debugFP, ">ICS: ");
1504 show_bytes(debugFP, buf, newcount);
1505 fprintf(debugFP, "\n");
1507 outcount = OutputToProcess(pr, buf, newcount, outError);
1508 if (outcount < newcount) return -1; /* to be sure */
1515 } else if (((unsigned char) *p) == TN_IAC) {
1516 *q++ = (char) TN_IAC;
1523 if (appData.debugMode) {
1524 fprintf(debugFP, ">ICS: ");
1525 show_bytes(debugFP, buf, newcount);
1526 fprintf(debugFP, "\n");
1528 outcount = OutputToProcess(pr, buf, newcount, outError);
1529 if (outcount < newcount) return -1; /* to be sure */
1534 read_from_player(isr, closure, message, count, error)
1541 int outError, outCount;
1542 static int gotEof = 0;
1544 /* Pass data read from player on to ICS */
1547 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1548 if (outCount < count) {
1549 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1551 } else if (count < 0) {
1552 RemoveInputSource(isr);
1553 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1554 } else if (gotEof++ > 0) {
1555 RemoveInputSource(isr);
1556 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1562 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1563 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1564 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1565 SendToICS("date\n");
1566 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1569 /* added routine for printf style output to ics */
1570 void ics_printf(char *format, ...)
1572 char buffer[MSG_SIZ];
1575 va_start(args, format);
1576 vsnprintf(buffer, sizeof(buffer), format, args);
1577 buffer[sizeof(buffer)-1] = '\0';
1586 int count, outCount, outError;
1588 if (icsPR == NULL) return;
1591 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1592 if (outCount < count) {
1593 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1597 /* This is used for sending logon scripts to the ICS. Sending
1598 without a delay causes problems when using timestamp on ICC
1599 (at least on my machine). */
1601 SendToICSDelayed(s,msdelay)
1605 int count, outCount, outError;
1607 if (icsPR == NULL) return;
1610 if (appData.debugMode) {
1611 fprintf(debugFP, ">ICS: ");
1612 show_bytes(debugFP, s, count);
1613 fprintf(debugFP, "\n");
1615 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1617 if (outCount < count) {
1618 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1623 /* Remove all highlighting escape sequences in s
1624 Also deletes any suffix starting with '('
1627 StripHighlightAndTitle(s)
1630 static char retbuf[MSG_SIZ];
1633 while (*s != NULLCHAR) {
1634 while (*s == '\033') {
1635 while (*s != NULLCHAR && !isalpha(*s)) s++;
1636 if (*s != NULLCHAR) s++;
1638 while (*s != NULLCHAR && *s != '\033') {
1639 if (*s == '(' || *s == '[') {
1650 /* Remove all highlighting escape sequences in s */
1655 static char retbuf[MSG_SIZ];
1658 while (*s != NULLCHAR) {
1659 while (*s == '\033') {
1660 while (*s != NULLCHAR && !isalpha(*s)) s++;
1661 if (*s != NULLCHAR) s++;
1663 while (*s != NULLCHAR && *s != '\033') {
1671 char *variantNames[] = VARIANT_NAMES;
1676 return variantNames[v];
1680 /* Identify a variant from the strings the chess servers use or the
1681 PGN Variant tag names we use. */
1688 VariantClass v = VariantNormal;
1689 int i, found = FALSE;
1695 /* [HGM] skip over optional board-size prefixes */
1696 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1697 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1698 while( *e++ != '_');
1701 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1705 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1706 if (StrCaseStr(e, variantNames[i])) {
1707 v = (VariantClass) i;
1714 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1715 || StrCaseStr(e, "wild/fr")
1716 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1717 v = VariantFischeRandom;
1718 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1719 (i = 1, p = StrCaseStr(e, "w"))) {
1721 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1728 case 0: /* FICS only, actually */
1730 /* Castling legal even if K starts on d-file */
1731 v = VariantWildCastle;
1736 /* Castling illegal even if K & R happen to start in
1737 normal positions. */
1738 v = VariantNoCastle;
1751 /* Castling legal iff K & R start in normal positions */
1757 /* Special wilds for position setup; unclear what to do here */
1758 v = VariantLoadable;
1761 /* Bizarre ICC game */
1762 v = VariantTwoKings;
1765 v = VariantKriegspiel;
1771 v = VariantFischeRandom;
1774 v = VariantCrazyhouse;
1777 v = VariantBughouse;
1783 /* Not quite the same as FICS suicide! */
1784 v = VariantGiveaway;
1790 v = VariantShatranj;
1793 /* Temporary names for future ICC types. The name *will* change in
1794 the next xboard/WinBoard release after ICC defines it. */
1832 v = VariantCapablanca;
1835 v = VariantKnightmate;
1841 v = VariantCylinder;
1847 v = VariantCapaRandom;
1850 v = VariantBerolina;
1862 /* Found "wild" or "w" in the string but no number;
1863 must assume it's normal chess. */
1867 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1868 if( (len > MSG_SIZ) && appData.debugMode )
1869 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1871 DisplayError(buf, 0);
1877 if (appData.debugMode) {
1878 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1879 e, wnum, VariantName(v));
1884 static int leftover_start = 0, leftover_len = 0;
1885 char star_match[STAR_MATCH_N][MSG_SIZ];
1887 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1888 advance *index beyond it, and set leftover_start to the new value of
1889 *index; else return FALSE. If pattern contains the character '*', it
1890 matches any sequence of characters not containing '\r', '\n', or the
1891 character following the '*' (if any), and the matched sequence(s) are
1892 copied into star_match.
1895 looking_at(buf, index, pattern)
1900 char *bufp = &buf[*index], *patternp = pattern;
1902 char *matchp = star_match[0];
1905 if (*patternp == NULLCHAR) {
1906 *index = leftover_start = bufp - buf;
1910 if (*bufp == NULLCHAR) return FALSE;
1911 if (*patternp == '*') {
1912 if (*bufp == *(patternp + 1)) {
1914 matchp = star_match[++star_count];
1918 } else if (*bufp == '\n' || *bufp == '\r') {
1920 if (*patternp == NULLCHAR)
1925 *matchp++ = *bufp++;
1929 if (*patternp != *bufp) return FALSE;
1936 SendToPlayer(data, length)
1940 int error, outCount;
1941 outCount = OutputToProcess(NoProc, data, length, &error);
1942 if (outCount < length) {
1943 DisplayFatalError(_("Error writing to display"), error, 1);
1948 PackHolding(packed, holding)
1960 switch (runlength) {
1971 sprintf(q, "%d", runlength);
1983 /* Telnet protocol requests from the front end */
1985 TelnetRequest(ddww, option)
1986 unsigned char ddww, option;
1988 unsigned char msg[3];
1989 int outCount, outError;
1991 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1993 if (appData.debugMode) {
1994 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2010 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2019 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2022 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2027 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2029 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2036 if (!appData.icsActive) return;
2037 TelnetRequest(TN_DO, TN_ECHO);
2043 if (!appData.icsActive) return;
2044 TelnetRequest(TN_DONT, TN_ECHO);
2048 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2050 /* put the holdings sent to us by the server on the board holdings area */
2051 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2055 if(gameInfo.holdingsWidth < 2) return;
2056 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2057 return; // prevent overwriting by pre-board holdings
2059 if( (int)lowestPiece >= BlackPawn ) {
2062 holdingsStartRow = BOARD_HEIGHT-1;
2065 holdingsColumn = BOARD_WIDTH-1;
2066 countsColumn = BOARD_WIDTH-2;
2067 holdingsStartRow = 0;
2071 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2072 board[i][holdingsColumn] = EmptySquare;
2073 board[i][countsColumn] = (ChessSquare) 0;
2075 while( (p=*holdings++) != NULLCHAR ) {
2076 piece = CharToPiece( ToUpper(p) );
2077 if(piece == EmptySquare) continue;
2078 /*j = (int) piece - (int) WhitePawn;*/
2079 j = PieceToNumber(piece);
2080 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2081 if(j < 0) continue; /* should not happen */
2082 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2083 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2084 board[holdingsStartRow+j*direction][countsColumn]++;
2090 VariantSwitch(Board board, VariantClass newVariant)
2092 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2093 static Board oldBoard;
2095 startedFromPositionFile = FALSE;
2096 if(gameInfo.variant == newVariant) return;
2098 /* [HGM] This routine is called each time an assignment is made to
2099 * gameInfo.variant during a game, to make sure the board sizes
2100 * are set to match the new variant. If that means adding or deleting
2101 * holdings, we shift the playing board accordingly
2102 * This kludge is needed because in ICS observe mode, we get boards
2103 * of an ongoing game without knowing the variant, and learn about the
2104 * latter only later. This can be because of the move list we requested,
2105 * in which case the game history is refilled from the beginning anyway,
2106 * but also when receiving holdings of a crazyhouse game. In the latter
2107 * case we want to add those holdings to the already received position.
2111 if (appData.debugMode) {
2112 fprintf(debugFP, "Switch board from %s to %s\n",
2113 VariantName(gameInfo.variant), VariantName(newVariant));
2114 setbuf(debugFP, NULL);
2116 shuffleOpenings = 0; /* [HGM] shuffle */
2117 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2121 newWidth = 9; newHeight = 9;
2122 gameInfo.holdingsSize = 7;
2123 case VariantBughouse:
2124 case VariantCrazyhouse:
2125 newHoldingsWidth = 2; break;
2129 newHoldingsWidth = 2;
2130 gameInfo.holdingsSize = 8;
2133 case VariantCapablanca:
2134 case VariantCapaRandom:
2137 newHoldingsWidth = gameInfo.holdingsSize = 0;
2140 if(newWidth != gameInfo.boardWidth ||
2141 newHeight != gameInfo.boardHeight ||
2142 newHoldingsWidth != gameInfo.holdingsWidth ) {
2144 /* shift position to new playing area, if needed */
2145 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2146 for(i=0; i<BOARD_HEIGHT; i++)
2147 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2148 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2150 for(i=0; i<newHeight; i++) {
2151 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2152 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2154 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2155 for(i=0; i<BOARD_HEIGHT; i++)
2156 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2157 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2160 gameInfo.boardWidth = newWidth;
2161 gameInfo.boardHeight = newHeight;
2162 gameInfo.holdingsWidth = newHoldingsWidth;
2163 gameInfo.variant = newVariant;
2164 InitDrawingSizes(-2, 0);
2165 } else gameInfo.variant = newVariant;
2166 CopyBoard(oldBoard, board); // remember correctly formatted board
2167 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2168 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2171 static int loggedOn = FALSE;
2173 /*-- Game start info cache: --*/
2175 char gs_kind[MSG_SIZ];
2176 static char player1Name[128] = "";
2177 static char player2Name[128] = "";
2178 static char cont_seq[] = "\n\\ ";
2179 static int player1Rating = -1;
2180 static int player2Rating = -1;
2181 /*----------------------------*/
2183 ColorClass curColor = ColorNormal;
2184 int suppressKibitz = 0;
2187 Boolean soughtPending = FALSE;
2188 Boolean seekGraphUp;
2189 #define MAX_SEEK_ADS 200
2191 char *seekAdList[MAX_SEEK_ADS];
2192 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2193 float tcList[MAX_SEEK_ADS];
2194 char colorList[MAX_SEEK_ADS];
2195 int nrOfSeekAds = 0;
2196 int minRating = 1010, maxRating = 2800;
2197 int hMargin = 10, vMargin = 20, h, w;
2198 extern int squareSize, lineGap;
2203 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2204 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2205 if(r < minRating+100 && r >=0 ) r = minRating+100;
2206 if(r > maxRating) r = maxRating;
2207 if(tc < 1.) tc = 1.;
2208 if(tc > 95.) tc = 95.;
2209 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2210 y = ((double)r - minRating)/(maxRating - minRating)
2211 * (h-vMargin-squareSize/8-1) + vMargin;
2212 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2213 if(strstr(seekAdList[i], " u ")) color = 1;
2214 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2215 !strstr(seekAdList[i], "bullet") &&
2216 !strstr(seekAdList[i], "blitz") &&
2217 !strstr(seekAdList[i], "standard") ) color = 2;
2218 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2219 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2223 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2225 char buf[MSG_SIZ], *ext = "";
2226 VariantClass v = StringToVariant(type);
2227 if(strstr(type, "wild")) {
2228 ext = type + 4; // append wild number
2229 if(v == VariantFischeRandom) type = "chess960"; else
2230 if(v == VariantLoadable) type = "setup"; else
2231 type = VariantName(v);
2233 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2234 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2235 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2236 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2237 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2238 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2239 seekNrList[nrOfSeekAds] = nr;
2240 zList[nrOfSeekAds] = 0;
2241 seekAdList[nrOfSeekAds++] = StrSave(buf);
2242 if(plot) PlotSeekAd(nrOfSeekAds-1);
2249 int x = xList[i], y = yList[i], d=squareSize/4, k;
2250 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2251 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2252 // now replot every dot that overlapped
2253 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2254 int xx = xList[k], yy = yList[k];
2255 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2256 DrawSeekDot(xx, yy, colorList[k]);
2261 RemoveSeekAd(int nr)
2264 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2266 if(seekAdList[i]) free(seekAdList[i]);
2267 seekAdList[i] = seekAdList[--nrOfSeekAds];
2268 seekNrList[i] = seekNrList[nrOfSeekAds];
2269 ratingList[i] = ratingList[nrOfSeekAds];
2270 colorList[i] = colorList[nrOfSeekAds];
2271 tcList[i] = tcList[nrOfSeekAds];
2272 xList[i] = xList[nrOfSeekAds];
2273 yList[i] = yList[nrOfSeekAds];
2274 zList[i] = zList[nrOfSeekAds];
2275 seekAdList[nrOfSeekAds] = NULL;
2281 MatchSoughtLine(char *line)
2283 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2284 int nr, base, inc, u=0; char dummy;
2286 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2287 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2289 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2290 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2291 // match: compact and save the line
2292 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2302 if(!seekGraphUp) return FALSE;
2303 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2304 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2306 DrawSeekBackground(0, 0, w, h);
2307 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2308 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2309 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2310 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2312 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2315 snprintf(buf, MSG_SIZ, "%d", i);
2316 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2319 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2320 for(i=1; i<100; i+=(i<10?1:5)) {
2321 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2322 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2323 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2325 snprintf(buf, MSG_SIZ, "%d", i);
2326 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2329 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2333 int SeekGraphClick(ClickType click, int x, int y, int moving)
2335 static int lastDown = 0, displayed = 0, lastSecond;
2336 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2337 if(click == Release || moving) return FALSE;
2339 soughtPending = TRUE;
2340 SendToICS(ics_prefix);
2341 SendToICS("sought\n"); // should this be "sought all"?
2342 } else { // issue challenge based on clicked ad
2343 int dist = 10000; int i, closest = 0, second = 0;
2344 for(i=0; i<nrOfSeekAds; i++) {
2345 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2346 if(d < dist) { dist = d; closest = i; }
2347 second += (d - zList[i] < 120); // count in-range ads
2348 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2352 second = (second > 1);
2353 if(displayed != closest || second != lastSecond) {
2354 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2355 lastSecond = second; displayed = closest;
2357 if(click == Press) {
2358 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2361 } // on press 'hit', only show info
2362 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2363 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2364 SendToICS(ics_prefix);
2366 return TRUE; // let incoming board of started game pop down the graph
2367 } else if(click == Release) { // release 'miss' is ignored
2368 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2369 if(moving == 2) { // right up-click
2370 nrOfSeekAds = 0; // refresh graph
2371 soughtPending = TRUE;
2372 SendToICS(ics_prefix);
2373 SendToICS("sought\n"); // should this be "sought all"?
2376 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2377 // press miss or release hit 'pop down' seek graph
2378 seekGraphUp = FALSE;
2379 DrawPosition(TRUE, NULL);
2385 read_from_ics(isr, closure, data, count, error)
2392 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2393 #define STARTED_NONE 0
2394 #define STARTED_MOVES 1
2395 #define STARTED_BOARD 2
2396 #define STARTED_OBSERVE 3
2397 #define STARTED_HOLDINGS 4
2398 #define STARTED_CHATTER 5
2399 #define STARTED_COMMENT 6
2400 #define STARTED_MOVES_NOHIDE 7
2402 static int started = STARTED_NONE;
2403 static char parse[20000];
2404 static int parse_pos = 0;
2405 static char buf[BUF_SIZE + 1];
2406 static int firstTime = TRUE, intfSet = FALSE;
2407 static ColorClass prevColor = ColorNormal;
2408 static int savingComment = FALSE;
2409 static int cmatch = 0; // continuation sequence match
2416 int backup; /* [DM] For zippy color lines */
2418 char talker[MSG_SIZ]; // [HGM] chat
2421 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2423 if (appData.debugMode) {
2425 fprintf(debugFP, "<ICS: ");
2426 show_bytes(debugFP, data, count);
2427 fprintf(debugFP, "\n");
2431 if (appData.debugMode) { int f = forwardMostMove;
2432 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2433 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2434 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2437 /* If last read ended with a partial line that we couldn't parse,
2438 prepend it to the new read and try again. */
2439 if (leftover_len > 0) {
2440 for (i=0; i<leftover_len; i++)
2441 buf[i] = buf[leftover_start + i];
2444 /* copy new characters into the buffer */
2445 bp = buf + leftover_len;
2446 buf_len=leftover_len;
2447 for (i=0; i<count; i++)
2450 if (data[i] == '\r')
2453 // join lines split by ICS?
2454 if (!appData.noJoin)
2457 Joining just consists of finding matches against the
2458 continuation sequence, and discarding that sequence
2459 if found instead of copying it. So, until a match
2460 fails, there's nothing to do since it might be the
2461 complete sequence, and thus, something we don't want
2464 if (data[i] == cont_seq[cmatch])
2467 if (cmatch == strlen(cont_seq))
2469 cmatch = 0; // complete match. just reset the counter
2472 it's possible for the ICS to not include the space
2473 at the end of the last word, making our [correct]
2474 join operation fuse two separate words. the server
2475 does this when the space occurs at the width setting.
2477 if (!buf_len || buf[buf_len-1] != ' ')
2488 match failed, so we have to copy what matched before
2489 falling through and copying this character. In reality,
2490 this will only ever be just the newline character, but
2491 it doesn't hurt to be precise.
2493 strncpy(bp, cont_seq, cmatch);
2505 buf[buf_len] = NULLCHAR;
2506 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2511 while (i < buf_len) {
2512 /* Deal with part of the TELNET option negotiation
2513 protocol. We refuse to do anything beyond the
2514 defaults, except that we allow the WILL ECHO option,
2515 which ICS uses to turn off password echoing when we are
2516 directly connected to it. We reject this option
2517 if localLineEditing mode is on (always on in xboard)
2518 and we are talking to port 23, which might be a real
2519 telnet server that will try to keep WILL ECHO on permanently.
2521 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2522 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2523 unsigned char option;
2525 switch ((unsigned char) buf[++i]) {
2527 if (appData.debugMode)
2528 fprintf(debugFP, "\n<WILL ");
2529 switch (option = (unsigned char) buf[++i]) {
2531 if (appData.debugMode)
2532 fprintf(debugFP, "ECHO ");
2533 /* Reply only if this is a change, according
2534 to the protocol rules. */
2535 if (remoteEchoOption) break;
2536 if (appData.localLineEditing &&
2537 atoi(appData.icsPort) == TN_PORT) {
2538 TelnetRequest(TN_DONT, TN_ECHO);
2541 TelnetRequest(TN_DO, TN_ECHO);
2542 remoteEchoOption = TRUE;
2546 if (appData.debugMode)
2547 fprintf(debugFP, "%d ", option);
2548 /* Whatever this is, we don't want it. */
2549 TelnetRequest(TN_DONT, option);
2554 if (appData.debugMode)
2555 fprintf(debugFP, "\n<WONT ");
2556 switch (option = (unsigned char) buf[++i]) {
2558 if (appData.debugMode)
2559 fprintf(debugFP, "ECHO ");
2560 /* Reply only if this is a change, according
2561 to the protocol rules. */
2562 if (!remoteEchoOption) break;
2564 TelnetRequest(TN_DONT, TN_ECHO);
2565 remoteEchoOption = FALSE;
2568 if (appData.debugMode)
2569 fprintf(debugFP, "%d ", (unsigned char) option);
2570 /* Whatever this is, it must already be turned
2571 off, because we never agree to turn on
2572 anything non-default, so according to the
2573 protocol rules, we don't reply. */
2578 if (appData.debugMode)
2579 fprintf(debugFP, "\n<DO ");
2580 switch (option = (unsigned char) buf[++i]) {
2582 /* Whatever this is, we refuse to do it. */
2583 if (appData.debugMode)
2584 fprintf(debugFP, "%d ", option);
2585 TelnetRequest(TN_WONT, option);
2590 if (appData.debugMode)
2591 fprintf(debugFP, "\n<DONT ");
2592 switch (option = (unsigned char) buf[++i]) {
2594 if (appData.debugMode)
2595 fprintf(debugFP, "%d ", option);
2596 /* Whatever this is, we are already not doing
2597 it, because we never agree to do anything
2598 non-default, so according to the protocol
2599 rules, we don't reply. */
2604 if (appData.debugMode)
2605 fprintf(debugFP, "\n<IAC ");
2606 /* Doubled IAC; pass it through */
2610 if (appData.debugMode)
2611 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2612 /* Drop all other telnet commands on the floor */
2615 if (oldi > next_out)
2616 SendToPlayer(&buf[next_out], oldi - next_out);
2622 /* OK, this at least will *usually* work */
2623 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2627 if (loggedOn && !intfSet) {
2628 if (ics_type == ICS_ICC) {
2629 snprintf(str, MSG_SIZ,
2630 "/set-quietly interface %s\n/set-quietly style 12\n",
2632 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2633 strcat(str, "/set-2 51 1\n/set seek 1\n");
2634 } else if (ics_type == ICS_CHESSNET) {
2635 snprintf(str, MSG_SIZ, "/style 12\n");
2637 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2638 strcat(str, programVersion);
2639 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2640 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2641 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2643 strcat(str, "$iset nohighlight 1\n");
2645 strcat(str, "$iset lock 1\n$style 12\n");
2648 NotifyFrontendLogin();
2652 if (started == STARTED_COMMENT) {
2653 /* Accumulate characters in comment */
2654 parse[parse_pos++] = buf[i];
2655 if (buf[i] == '\n') {
2656 parse[parse_pos] = NULLCHAR;
2657 if(chattingPartner>=0) {
2659 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2660 OutputChatMessage(chattingPartner, mess);
2661 chattingPartner = -1;
2662 next_out = i+1; // [HGM] suppress printing in ICS window
2664 if(!suppressKibitz) // [HGM] kibitz
2665 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2666 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2667 int nrDigit = 0, nrAlph = 0, j;
2668 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2669 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2670 parse[parse_pos] = NULLCHAR;
2671 // try to be smart: if it does not look like search info, it should go to
2672 // ICS interaction window after all, not to engine-output window.
2673 for(j=0; j<parse_pos; j++) { // count letters and digits
2674 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2675 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2676 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2678 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2679 int depth=0; float score;
2680 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2681 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2682 pvInfoList[forwardMostMove-1].depth = depth;
2683 pvInfoList[forwardMostMove-1].score = 100*score;
2685 OutputKibitz(suppressKibitz, parse);
2688 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2689 SendToPlayer(tmp, strlen(tmp));
2691 next_out = i+1; // [HGM] suppress printing in ICS window
2693 started = STARTED_NONE;
2695 /* Don't match patterns against characters in comment */
2700 if (started == STARTED_CHATTER) {
2701 if (buf[i] != '\n') {
2702 /* Don't match patterns against characters in chatter */
2706 started = STARTED_NONE;
2707 if(suppressKibitz) next_out = i+1;
2710 /* Kludge to deal with rcmd protocol */
2711 if (firstTime && looking_at(buf, &i, "\001*")) {
2712 DisplayFatalError(&buf[1], 0, 1);
2718 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2721 if (appData.debugMode)
2722 fprintf(debugFP, "ics_type %d\n", ics_type);
2725 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2726 ics_type = ICS_FICS;
2728 if (appData.debugMode)
2729 fprintf(debugFP, "ics_type %d\n", ics_type);
2732 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2733 ics_type = ICS_CHESSNET;
2735 if (appData.debugMode)
2736 fprintf(debugFP, "ics_type %d\n", ics_type);
2741 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2742 looking_at(buf, &i, "Logging you in as \"*\"") ||
2743 looking_at(buf, &i, "will be \"*\""))) {
2744 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2748 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2750 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2751 DisplayIcsInteractionTitle(buf);
2752 have_set_title = TRUE;
2755 /* skip finger notes */
2756 if (started == STARTED_NONE &&
2757 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2758 (buf[i] == '1' && buf[i+1] == '0')) &&
2759 buf[i+2] == ':' && buf[i+3] == ' ') {
2760 started = STARTED_CHATTER;
2766 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2767 if(appData.seekGraph) {
2768 if(soughtPending && MatchSoughtLine(buf+i)) {
2769 i = strstr(buf+i, "rated") - buf;
2770 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2771 next_out = leftover_start = i;
2772 started = STARTED_CHATTER;
2773 suppressKibitz = TRUE;
2776 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2777 && looking_at(buf, &i, "* ads displayed")) {
2778 soughtPending = FALSE;
2783 if(appData.autoRefresh) {
2784 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2785 int s = (ics_type == ICS_ICC); // ICC format differs
2787 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2788 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2789 looking_at(buf, &i, "*% "); // eat prompt
2790 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2791 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2792 next_out = i; // suppress
2795 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2796 char *p = star_match[0];
2798 if(seekGraphUp) RemoveSeekAd(atoi(p));
2799 while(*p && *p++ != ' '); // next
2801 looking_at(buf, &i, "*% "); // eat prompt
2802 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2809 /* skip formula vars */
2810 if (started == STARTED_NONE &&
2811 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2812 started = STARTED_CHATTER;
2817 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2818 if (appData.autoKibitz && started == STARTED_NONE &&
2819 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2820 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2821 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2822 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2823 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2824 suppressKibitz = TRUE;
2825 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2827 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2828 && (gameMode == IcsPlayingWhite)) ||
2829 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2830 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2831 started = STARTED_CHATTER; // own kibitz we simply discard
2833 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2834 parse_pos = 0; parse[0] = NULLCHAR;
2835 savingComment = TRUE;
2836 suppressKibitz = gameMode != IcsObserving ? 2 :
2837 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2841 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2842 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2843 && atoi(star_match[0])) {
2844 // suppress the acknowledgements of our own autoKibitz
2846 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2847 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2848 SendToPlayer(star_match[0], strlen(star_match[0]));
2849 if(looking_at(buf, &i, "*% ")) // eat prompt
2850 suppressKibitz = FALSE;
2854 } // [HGM] kibitz: end of patch
2856 // [HGM] chat: intercept tells by users for which we have an open chat window
2858 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2859 looking_at(buf, &i, "* whispers:") ||
2860 looking_at(buf, &i, "* kibitzes:") ||
2861 looking_at(buf, &i, "* shouts:") ||
2862 looking_at(buf, &i, "* c-shouts:") ||
2863 looking_at(buf, &i, "--> * ") ||
2864 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2865 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2866 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2867 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2869 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2870 chattingPartner = -1;
2872 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2873 for(p=0; p<MAX_CHAT; p++) {
2874 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2875 talker[0] = '['; strcat(talker, "] ");
2876 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2877 chattingPartner = p; break;
2880 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2881 for(p=0; p<MAX_CHAT; p++) {
2882 if(!strcmp("kibitzes", chatPartner[p])) {
2883 talker[0] = '['; strcat(talker, "] ");
2884 chattingPartner = p; break;
2887 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2888 for(p=0; p<MAX_CHAT; p++) {
2889 if(!strcmp("whispers", chatPartner[p])) {
2890 talker[0] = '['; strcat(talker, "] ");
2891 chattingPartner = p; break;
2894 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2895 if(buf[i-8] == '-' && buf[i-3] == 't')
2896 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2897 if(!strcmp("c-shouts", chatPartner[p])) {
2898 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2899 chattingPartner = p; break;
2902 if(chattingPartner < 0)
2903 for(p=0; p<MAX_CHAT; p++) {
2904 if(!strcmp("shouts", chatPartner[p])) {
2905 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2906 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2907 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2908 chattingPartner = p; break;
2912 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2913 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2914 talker[0] = 0; Colorize(ColorTell, FALSE);
2915 chattingPartner = p; break;
2917 if(chattingPartner<0) i = oldi; else {
2918 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2919 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2920 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2921 started = STARTED_COMMENT;
2922 parse_pos = 0; parse[0] = NULLCHAR;
2923 savingComment = 3 + chattingPartner; // counts as TRUE
2924 suppressKibitz = TRUE;
2927 } // [HGM] chat: end of patch
2929 if (appData.zippyTalk || appData.zippyPlay) {
2930 /* [DM] Backup address for color zippy lines */
2933 if (loggedOn == TRUE)
2934 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2935 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2937 } // [DM] 'else { ' deleted
2939 /* Regular tells and says */
2940 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2941 looking_at(buf, &i, "* (your partner) tells you: ") ||
2942 looking_at(buf, &i, "* says: ") ||
2943 /* Don't color "message" or "messages" output */
2944 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2945 looking_at(buf, &i, "*. * at *:*: ") ||
2946 looking_at(buf, &i, "--* (*:*): ") ||
2947 /* Message notifications (same color as tells) */
2948 looking_at(buf, &i, "* has left a message ") ||
2949 looking_at(buf, &i, "* just sent you a message:\n") ||
2950 /* Whispers and kibitzes */
2951 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2952 looking_at(buf, &i, "* kibitzes: ") ||
2954 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2956 if (tkind == 1 && strchr(star_match[0], ':')) {
2957 /* Avoid "tells you:" spoofs in channels */
2960 if (star_match[0][0] == NULLCHAR ||
2961 strchr(star_match[0], ' ') ||
2962 (tkind == 3 && strchr(star_match[1], ' '))) {
2963 /* Reject bogus matches */
2966 if (appData.colorize) {
2967 if (oldi > next_out) {
2968 SendToPlayer(&buf[next_out], oldi - next_out);
2973 Colorize(ColorTell, FALSE);
2974 curColor = ColorTell;
2977 Colorize(ColorKibitz, FALSE);
2978 curColor = ColorKibitz;
2981 p = strrchr(star_match[1], '(');
2988 Colorize(ColorChannel1, FALSE);
2989 curColor = ColorChannel1;
2991 Colorize(ColorChannel, FALSE);
2992 curColor = ColorChannel;
2996 curColor = ColorNormal;
3000 if (started == STARTED_NONE && appData.autoComment &&
3001 (gameMode == IcsObserving ||
3002 gameMode == IcsPlayingWhite ||
3003 gameMode == IcsPlayingBlack)) {
3004 parse_pos = i - oldi;
3005 memcpy(parse, &buf[oldi], parse_pos);
3006 parse[parse_pos] = NULLCHAR;
3007 started = STARTED_COMMENT;
3008 savingComment = TRUE;
3010 started = STARTED_CHATTER;
3011 savingComment = FALSE;
3018 if (looking_at(buf, &i, "* s-shouts: ") ||
3019 looking_at(buf, &i, "* c-shouts: ")) {
3020 if (appData.colorize) {
3021 if (oldi > next_out) {
3022 SendToPlayer(&buf[next_out], oldi - next_out);
3025 Colorize(ColorSShout, FALSE);
3026 curColor = ColorSShout;
3029 started = STARTED_CHATTER;
3033 if (looking_at(buf, &i, "--->")) {
3038 if (looking_at(buf, &i, "* shouts: ") ||
3039 looking_at(buf, &i, "--> ")) {
3040 if (appData.colorize) {
3041 if (oldi > next_out) {
3042 SendToPlayer(&buf[next_out], oldi - next_out);
3045 Colorize(ColorShout, FALSE);
3046 curColor = ColorShout;
3049 started = STARTED_CHATTER;
3053 if (looking_at( buf, &i, "Challenge:")) {
3054 if (appData.colorize) {
3055 if (oldi > next_out) {
3056 SendToPlayer(&buf[next_out], oldi - next_out);
3059 Colorize(ColorChallenge, FALSE);
3060 curColor = ColorChallenge;
3066 if (looking_at(buf, &i, "* offers you") ||
3067 looking_at(buf, &i, "* offers to be") ||
3068 looking_at(buf, &i, "* would like to") ||
3069 looking_at(buf, &i, "* requests to") ||
3070 looking_at(buf, &i, "Your opponent offers") ||
3071 looking_at(buf, &i, "Your opponent requests")) {
3073 if (appData.colorize) {
3074 if (oldi > next_out) {
3075 SendToPlayer(&buf[next_out], oldi - next_out);
3078 Colorize(ColorRequest, FALSE);
3079 curColor = ColorRequest;
3084 if (looking_at(buf, &i, "* (*) seeking")) {
3085 if (appData.colorize) {
3086 if (oldi > next_out) {
3087 SendToPlayer(&buf[next_out], oldi - next_out);
3090 Colorize(ColorSeek, FALSE);
3091 curColor = ColorSeek;
3096 if (looking_at(buf, &i, "\\ ")) {
3097 if (prevColor != ColorNormal) {
3098 if (oldi > next_out) {
3099 SendToPlayer(&buf[next_out], oldi - next_out);
3102 Colorize(prevColor, TRUE);
3103 curColor = prevColor;
3105 if (savingComment) {
3106 parse_pos = i - oldi;
3107 memcpy(parse, &buf[oldi], parse_pos);
3108 parse[parse_pos] = NULLCHAR;
3109 started = STARTED_COMMENT;
3110 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3111 chattingPartner = savingComment - 3; // kludge to remember the box
3113 started = STARTED_CHATTER;
3118 if (looking_at(buf, &i, "Black Strength :") ||
3119 looking_at(buf, &i, "<<< style 10 board >>>") ||
3120 looking_at(buf, &i, "<10>") ||
3121 looking_at(buf, &i, "#@#")) {
3122 /* Wrong board style */
3124 SendToICS(ics_prefix);
3125 SendToICS("set style 12\n");
3126 SendToICS(ics_prefix);
3127 SendToICS("refresh\n");
3131 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3133 have_sent_ICS_logon = 1;
3137 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3138 (looking_at(buf, &i, "\n<12> ") ||
3139 looking_at(buf, &i, "<12> "))) {
3141 if (oldi > next_out) {
3142 SendToPlayer(&buf[next_out], oldi - next_out);
3145 started = STARTED_BOARD;
3150 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3151 looking_at(buf, &i, "<b1> ")) {
3152 if (oldi > next_out) {
3153 SendToPlayer(&buf[next_out], oldi - next_out);
3156 started = STARTED_HOLDINGS;
3161 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3163 /* Header for a move list -- first line */
3165 switch (ics_getting_history) {
3169 case BeginningOfGame:
3170 /* User typed "moves" or "oldmoves" while we
3171 were idle. Pretend we asked for these
3172 moves and soak them up so user can step
3173 through them and/or save them.
3176 gameMode = IcsObserving;
3179 ics_getting_history = H_GOT_UNREQ_HEADER;
3181 case EditGame: /*?*/
3182 case EditPosition: /*?*/
3183 /* Should above feature work in these modes too? */
3184 /* For now it doesn't */
3185 ics_getting_history = H_GOT_UNWANTED_HEADER;
3188 ics_getting_history = H_GOT_UNWANTED_HEADER;
3193 /* Is this the right one? */
3194 if (gameInfo.white && gameInfo.black &&
3195 strcmp(gameInfo.white, star_match[0]) == 0 &&
3196 strcmp(gameInfo.black, star_match[2]) == 0) {
3198 ics_getting_history = H_GOT_REQ_HEADER;
3201 case H_GOT_REQ_HEADER:
3202 case H_GOT_UNREQ_HEADER:
3203 case H_GOT_UNWANTED_HEADER:
3204 case H_GETTING_MOVES:
3205 /* Should not happen */
3206 DisplayError(_("Error gathering move list: two headers"), 0);
3207 ics_getting_history = H_FALSE;
3211 /* Save player ratings into gameInfo if needed */
3212 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3213 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3214 (gameInfo.whiteRating == -1 ||
3215 gameInfo.blackRating == -1)) {
3217 gameInfo.whiteRating = string_to_rating(star_match[1]);
3218 gameInfo.blackRating = string_to_rating(star_match[3]);
3219 if (appData.debugMode)
3220 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3221 gameInfo.whiteRating, gameInfo.blackRating);
3226 if (looking_at(buf, &i,
3227 "* * match, initial time: * minute*, increment: * second")) {
3228 /* Header for a move list -- second line */
3229 /* Initial board will follow if this is a wild game */
3230 if (gameInfo.event != NULL) free(gameInfo.event);
3231 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3232 gameInfo.event = StrSave(str);
3233 /* [HGM] we switched variant. Translate boards if needed. */
3234 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3238 if (looking_at(buf, &i, "Move ")) {
3239 /* Beginning of a move list */
3240 switch (ics_getting_history) {
3242 /* Normally should not happen */
3243 /* Maybe user hit reset while we were parsing */
3246 /* Happens if we are ignoring a move list that is not
3247 * the one we just requested. Common if the user
3248 * tries to observe two games without turning off
3251 case H_GETTING_MOVES:
3252 /* Should not happen */
3253 DisplayError(_("Error gathering move list: nested"), 0);
3254 ics_getting_history = H_FALSE;
3256 case H_GOT_REQ_HEADER:
3257 ics_getting_history = H_GETTING_MOVES;
3258 started = STARTED_MOVES;
3260 if (oldi > next_out) {
3261 SendToPlayer(&buf[next_out], oldi - next_out);
3264 case H_GOT_UNREQ_HEADER:
3265 ics_getting_history = H_GETTING_MOVES;
3266 started = STARTED_MOVES_NOHIDE;
3269 case H_GOT_UNWANTED_HEADER:
3270 ics_getting_history = H_FALSE;
3276 if (looking_at(buf, &i, "% ") ||
3277 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3278 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3279 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3280 soughtPending = FALSE;
3284 if(suppressKibitz) next_out = i;
3285 savingComment = FALSE;
3289 case STARTED_MOVES_NOHIDE:
3290 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3291 parse[parse_pos + i - oldi] = NULLCHAR;
3292 ParseGameHistory(parse);
3294 if (appData.zippyPlay && first.initDone) {
3295 FeedMovesToProgram(&first, forwardMostMove);
3296 if (gameMode == IcsPlayingWhite) {
3297 if (WhiteOnMove(forwardMostMove)) {
3298 if (first.sendTime) {
3299 if (first.useColors) {
3300 SendToProgram("black\n", &first);
3302 SendTimeRemaining(&first, TRUE);
3304 if (first.useColors) {
3305 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3307 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3308 first.maybeThinking = TRUE;
3310 if (first.usePlayother) {
3311 if (first.sendTime) {
3312 SendTimeRemaining(&first, TRUE);
3314 SendToProgram("playother\n", &first);
3320 } else if (gameMode == IcsPlayingBlack) {
3321 if (!WhiteOnMove(forwardMostMove)) {
3322 if (first.sendTime) {
3323 if (first.useColors) {
3324 SendToProgram("white\n", &first);
3326 SendTimeRemaining(&first, FALSE);
3328 if (first.useColors) {
3329 SendToProgram("black\n", &first);
3331 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3332 first.maybeThinking = TRUE;
3334 if (first.usePlayother) {
3335 if (first.sendTime) {
3336 SendTimeRemaining(&first, FALSE);
3338 SendToProgram("playother\n", &first);
3347 if (gameMode == IcsObserving && ics_gamenum == -1) {
3348 /* Moves came from oldmoves or moves command
3349 while we weren't doing anything else.
3351 currentMove = forwardMostMove;
3352 ClearHighlights();/*!!could figure this out*/
3353 flipView = appData.flipView;
3354 DrawPosition(TRUE, boards[currentMove]);
3355 DisplayBothClocks();
3356 snprintf(str, MSG_SIZ, "%s&nbs