security fix: replaced sprintf with snprintf
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
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.
24  *
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
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
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.
42  *
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.
47  *
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/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
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,
169                                                                                 Board board));
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));
194
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);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
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 */
244
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 */
248 int endPV = -1;
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];
256 Boolean partnerUp;
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;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 {
315   /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
316    *
317    * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
318    */
319
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   strncpy( dst, src, count );
325   if(  dst[ count-1 ] != '\0' )
326     {
327       if(appData.debugMode)
328       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330   dst[ count-1 ] = '\0';
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble(u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags(index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP;
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int have_sent_ICS_logon = 0;
453 int movesPerSession;
454 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
455 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
456 long timeControl_2; /* [AS] Allow separate time controls */
457 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
458 long timeRemaining[2][MAX_MOVES];
459 int matchGame = 0;
460 TimeMark programStartTime;
461 char ics_handle[MSG_SIZ];
462 int have_set_title = 0;
463
464 /* animateTraining preserves the state of appData.animate
465  * when Training mode is activated. This allows the
466  * response to be animated when appData.animate == TRUE and
467  * appData.animateDragging == TRUE.
468  */
469 Boolean animateTraining;
470
471 GameInfo gameInfo;
472
473 AppData appData;
474
475 Board boards[MAX_MOVES];
476 /* [HGM] Following 7 needed for accurate legality tests: */
477 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
478 signed char  initialRights[BOARD_FILES];
479 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
480 int   initialRulePlies, FENrulePlies;
481 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
482 int loadFlag = 0;
483 int shuffleOpenings;
484 int mute; // mute all sounds
485
486 // [HGM] vari: next 12 to save and restore variations
487 #define MAX_VARIATIONS 10
488 int framePtr = MAX_MOVES-1; // points to free stack entry
489 int storedGames = 0;
490 int savedFirst[MAX_VARIATIONS];
491 int savedLast[MAX_VARIATIONS];
492 int savedFramePtr[MAX_VARIATIONS];
493 char *savedDetails[MAX_VARIATIONS];
494 ChessMove savedResult[MAX_VARIATIONS];
495
496 void PushTail P((int firstMove, int lastMove));
497 Boolean PopTail P((Boolean annotate));
498 void CleanupTail P((void));
499
500 ChessSquare  FIDEArray[2][BOARD_FILES] = {
501     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
502         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
504         BlackKing, BlackBishop, BlackKnight, BlackRook }
505 };
506
507 ChessSquare twoKingsArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackKing, BlackKnight, BlackRook }
512 };
513
514 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
516         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
517     { BlackRook, BlackMan, BlackBishop, BlackQueen,
518         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
519 };
520
521 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
524     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
525         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
526 };
527
528 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
529     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
530         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
531     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
532         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
533 };
534
535 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
536     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
537         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
538     { BlackRook, BlackKnight, BlackMan, BlackFerz,
539         BlackKing, BlackMan, BlackKnight, BlackRook }
540 };
541
542
543 #if (BOARD_FILES>=10)
544 ChessSquare ShogiArray[2][BOARD_FILES] = {
545     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
546         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
547     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
548         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
549 };
550
551 ChessSquare XiangqiArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
553         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
555         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 };
557
558 ChessSquare CapablancaArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
560         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
562         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
563 };
564
565 ChessSquare GreatArray[2][BOARD_FILES] = {
566     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
567         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
568     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
569         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
570 };
571
572 ChessSquare JanusArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
574         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
575     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
576         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
577 };
578
579 #ifdef GOTHIC
580 ChessSquare GothicArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
582         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
584         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
585 };
586 #else // !GOTHIC
587 #define GothicArray CapablancaArray
588 #endif // !GOTHIC
589
590 #ifdef FALCON
591 ChessSquare FalconArray[2][BOARD_FILES] = {
592     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
593         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
595         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
596 };
597 #else // !FALCON
598 #define FalconArray CapablancaArray
599 #endif // !FALCON
600
601 #else // !(BOARD_FILES>=10)
602 #define XiangqiPosition FIDEArray
603 #define CapablancaArray FIDEArray
604 #define GothicArray FIDEArray
605 #define GreatArray FIDEArray
606 #endif // !(BOARD_FILES>=10)
607
608 #if (BOARD_FILES>=12)
609 ChessSquare CourierArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
611         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
613         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
614 };
615 #else // !(BOARD_FILES>=12)
616 #define CourierArray CapablancaArray
617 #endif // !(BOARD_FILES>=12)
618
619
620 Board initialPosition;
621
622
623 /* Convert str to a rating. Checks for special cases of "----",
624
625    "++++", etc. Also strips ()'s */
626 int
627 string_to_rating(str)
628   char *str;
629 {
630   while(*str && !isdigit(*str)) ++str;
631   if (!*str)
632     return 0;   /* One of the special "no rating" cases */
633   else
634     return atoi(str);
635 }
636
637 void
638 ClearProgramStats()
639 {
640     /* Init programStats */
641     programStats.movelist[0] = 0;
642     programStats.depth = 0;
643     programStats.nr_moves = 0;
644     programStats.moves_left = 0;
645     programStats.nodes = 0;
646     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
647     programStats.score = 0;
648     programStats.got_only_move = 0;
649     programStats.got_fail = 0;
650     programStats.line_is_book = 0;
651 }
652
653 void
654 InitBackEnd1()
655 {
656     int matched, min, sec;
657
658     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
659     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
660
661     GetTimeMark(&programStartTime);
662     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
663
664     ClearProgramStats();
665     programStats.ok_to_send = 1;
666     programStats.seen_stat = 0;
667
668     /*
669      * Initialize game list
670      */
671     ListNew(&gameList);
672
673
674     /*
675      * Internet chess server status
676      */
677     if (appData.icsActive) {
678         appData.matchMode = FALSE;
679         appData.matchGames = 0;
680 #if ZIPPY
681         appData.noChessProgram = !appData.zippyPlay;
682 #else
683         appData.zippyPlay = FALSE;
684         appData.zippyTalk = FALSE;
685         appData.noChessProgram = TRUE;
686 #endif
687         if (*appData.icsHelper != NULLCHAR) {
688             appData.useTelnet = TRUE;
689             appData.telnetProgram = appData.icsHelper;
690         }
691     } else {
692         appData.zippyTalk = appData.zippyPlay = FALSE;
693     }
694
695     /* [AS] Initialize pv info list [HGM] and game state */
696     {
697         int i, j;
698
699         for( i=0; i<=framePtr; i++ ) {
700             pvInfoList[i].depth = -1;
701             boards[i][EP_STATUS] = EP_NONE;
702             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
703         }
704     }
705
706     /*
707      * Parse timeControl resource
708      */
709     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
710                           appData.movesPerSession)) {
711         char buf[MSG_SIZ];
712         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
713         DisplayFatalError(buf, 0, 2);
714     }
715
716     /*
717      * Parse searchTime resource
718      */
719     if (*appData.searchTime != NULLCHAR) {
720         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
721         if (matched == 1) {
722             searchTime = min * 60;
723         } else if (matched == 2) {
724             searchTime = min * 60 + sec;
725         } else {
726             char buf[MSG_SIZ];
727             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
728             DisplayFatalError(buf, 0, 2);
729         }
730     }
731
732     /* [AS] Adjudication threshold */
733     adjudicateLossThreshold = appData.adjudicateLossThreshold;
734
735     first.which = _("first");
736     second.which = _("second");
737     first.maybeThinking = second.maybeThinking = FALSE;
738     first.pr = second.pr = NoProc;
739     first.isr = second.isr = NULL;
740     first.sendTime = second.sendTime = 2;
741     first.sendDrawOffers = 1;
742     if (appData.firstPlaysBlack) {
743         first.twoMachinesColor = "black\n";
744         second.twoMachinesColor = "white\n";
745     } else {
746         first.twoMachinesColor = "white\n";
747         second.twoMachinesColor = "black\n";
748     }
749     first.program = appData.firstChessProgram;
750     second.program = appData.secondChessProgram;
751     first.host = appData.firstHost;
752     second.host = appData.secondHost;
753     first.dir = appData.firstDirectory;
754     second.dir = appData.secondDirectory;
755     first.other = &second;
756     second.other = &first;
757     first.initString = appData.initString;
758     second.initString = appData.secondInitString;
759     first.computerString = appData.firstComputerString;
760     second.computerString = appData.secondComputerString;
761     first.useSigint = second.useSigint = TRUE;
762     first.useSigterm = second.useSigterm = TRUE;
763     first.reuse = appData.reuseFirst;
764     second.reuse = appData.reuseSecond;
765     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
766     second.nps = appData.secondNPS;
767     first.useSetboard = second.useSetboard = FALSE;
768     first.useSAN = second.useSAN = FALSE;
769     first.usePing = second.usePing = FALSE;
770     first.lastPing = second.lastPing = 0;
771     first.lastPong = second.lastPong = 0;
772     first.usePlayother = second.usePlayother = FALSE;
773     first.useColors = second.useColors = TRUE;
774     first.useUsermove = second.useUsermove = FALSE;
775     first.sendICS = second.sendICS = FALSE;
776     first.sendName = second.sendName = appData.icsActive;
777     first.sdKludge = second.sdKludge = FALSE;
778     first.stKludge = second.stKludge = FALSE;
779     TidyProgramName(first.program, first.host, first.tidy);
780     TidyProgramName(second.program, second.host, second.tidy);
781     first.matchWins = second.matchWins = 0;
782     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
783     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
784     first.analysisSupport = second.analysisSupport = 2; /* detect */
785     first.analyzing = second.analyzing = FALSE;
786     first.initDone = second.initDone = FALSE;
787
788     /* New features added by Tord: */
789     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
790     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
791     /* End of new features added by Tord. */
792     first.fenOverride  = appData.fenOverride1;
793     second.fenOverride = appData.fenOverride2;
794
795     /* [HGM] time odds: set factor for each machine */
796     first.timeOdds  = appData.firstTimeOdds;
797     second.timeOdds = appData.secondTimeOdds;
798     { float norm = 1;
799         if(appData.timeOddsMode) {
800             norm = first.timeOdds;
801             if(norm > second.timeOdds) norm = second.timeOdds;
802         }
803         first.timeOdds /= norm;
804         second.timeOdds /= norm;
805     }
806
807     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
808     first.accumulateTC = appData.firstAccumulateTC;
809     second.accumulateTC = appData.secondAccumulateTC;
810     first.maxNrOfSessions = second.maxNrOfSessions = 1;
811
812     /* [HGM] debug */
813     first.debug = second.debug = FALSE;
814     first.supportsNPS = second.supportsNPS = UNKNOWN;
815
816     /* [HGM] options */
817     first.optionSettings  = appData.firstOptions;
818     second.optionSettings = appData.secondOptions;
819
820     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
821     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
822     first.isUCI = appData.firstIsUCI; /* [AS] */
823     second.isUCI = appData.secondIsUCI; /* [AS] */
824     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
825     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
826
827     if (appData.firstProtocolVersion > PROTOVER
828         || appData.firstProtocolVersion < 1)
829       {
830         char buf[MSG_SIZ];
831         int len;
832
833         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
834                        appData.firstProtocolVersion);
835         if( (len > MSG_SIZ) && appData.debugMode )
836           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
837
838         DisplayFatalError(buf, 0, 2);
839       }
840     else
841       {
842         first.protocolVersion = appData.firstProtocolVersion;
843       }
844
845     if (appData.secondProtocolVersion > PROTOVER
846         || appData.secondProtocolVersion < 1)
847       {
848         char buf[MSG_SIZ];
849         int len;
850
851         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
852                        appData.secondProtocolVersion);
853         if( (len > MSG_SIZ) && appData.debugMode )
854           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
855
856         DisplayFatalError(buf, 0, 2);
857       }
858     else
859       {
860         second.protocolVersion = appData.secondProtocolVersion;
861       }
862
863     if (appData.icsActive) {
864         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
865 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
866     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
867         appData.clockMode = FALSE;
868         first.sendTime = second.sendTime = 0;
869     }
870
871 #if ZIPPY
872     /* Override some settings from environment variables, for backward
873        compatibility.  Unfortunately it's not feasible to have the env
874        vars just set defaults, at least in xboard.  Ugh.
875     */
876     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
877       ZippyInit();
878     }
879 #endif
880
881     if (appData.noChessProgram) {
882         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
883         sprintf(programVersion, "%s", PACKAGE_STRING);
884     } else {
885       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
886       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
887       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
888     }
889
890     if (!appData.icsActive) {
891       char buf[MSG_SIZ];
892       int len;
893
894       /* Check for variants that are supported only in ICS mode,
895          or not at all.  Some that are accepted here nevertheless
896          have bugs; see comments below.
897       */
898       VariantClass variant = StringToVariant(appData.variant);
899       switch (variant) {
900       case VariantBughouse:     /* need four players and two boards */
901       case VariantKriegspiel:   /* need to hide pieces and move details */
902         /* case VariantFischeRandom: (Fabien: moved below) */
903         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
904         if( (len > MSG_SIZ) && appData.debugMode )
905           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
906
907         DisplayFatalError(buf, 0, 2);
908         return;
909
910       case VariantUnknown:
911       case VariantLoadable:
912       case Variant29:
913       case Variant30:
914       case Variant31:
915       case Variant32:
916       case Variant33:
917       case Variant34:
918       case Variant35:
919       case Variant36:
920       default:
921         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
922         if( (len > MSG_SIZ) && appData.debugMode )
923           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
924
925         DisplayFatalError(buf, 0, 2);
926         return;
927
928       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
929       case VariantFairy:      /* [HGM] TestLegality definitely off! */
930       case VariantGothic:     /* [HGM] should work */
931       case VariantCapablanca: /* [HGM] should work */
932       case VariantCourier:    /* [HGM] initial forced moves not implemented */
933       case VariantShogi:      /* [HGM] could still mate with pawn drop */
934       case VariantKnightmate: /* [HGM] should work */
935       case VariantCylinder:   /* [HGM] untested */
936       case VariantFalcon:     /* [HGM] untested */
937       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
938                                  offboard interposition not understood */
939       case VariantNormal:     /* definitely works! */
940       case VariantWildCastle: /* pieces not automatically shuffled */
941       case VariantNoCastle:   /* pieces not automatically shuffled */
942       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
943       case VariantLosers:     /* should work except for win condition,
944                                  and doesn't know captures are mandatory */
945       case VariantSuicide:    /* should work except for win condition,
946                                  and doesn't know captures are mandatory */
947       case VariantGiveaway:   /* should work except for win condition,
948                                  and doesn't know captures are mandatory */
949       case VariantTwoKings:   /* should work */
950       case VariantAtomic:     /* should work except for win condition */
951       case Variant3Check:     /* should work except for win condition */
952       case VariantShatranj:   /* should work except for all win conditions */
953       case VariantMakruk:     /* should work except for daw countdown */
954       case VariantBerolina:   /* might work if TestLegality is off */
955       case VariantCapaRandom: /* should work */
956       case VariantJanus:      /* should work */
957       case VariantSuper:      /* experimental */
958       case VariantGreat:      /* experimental, requires legality testing to be off */
959         break;
960       }
961     }
962
963     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
964     InitEngineUCI( installDir, &second );
965 }
966
967 int NextIntegerFromString( char ** str, long * value )
968 {
969     int result = -1;
970     char * s = *str;
971
972     while( *s == ' ' || *s == '\t' ) {
973         s++;
974     }
975
976     *value = 0;
977
978     if( *s >= '0' && *s <= '9' ) {
979         while( *s >= '0' && *s <= '9' ) {
980             *value = *value * 10 + (*s - '0');
981             s++;
982         }
983
984         result = 0;
985     }
986
987     *str = s;
988
989     return result;
990 }
991
992 int NextTimeControlFromString( char ** str, long * value )
993 {
994     long temp;
995     int result = NextIntegerFromString( str, &temp );
996
997     if( result == 0 ) {
998         *value = temp * 60; /* Minutes */
999         if( **str == ':' ) {
1000             (*str)++;
1001             result = NextIntegerFromString( str, &temp );
1002             *value += temp; /* Seconds */
1003         }
1004     }
1005
1006     return result;
1007 }
1008
1009 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1010 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1011     int result = -1, type = 0; long temp, temp2;
1012
1013     if(**str != ':') return -1; // old params remain in force!
1014     (*str)++;
1015     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1016     if( NextIntegerFromString( str, &temp ) ) return -1;
1017     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1018
1019     if(**str != '/') {
1020         /* time only: incremental or sudden-death time control */
1021         if(**str == '+') { /* increment follows; read it */
1022             (*str)++;
1023             if(**str == '!') type = *(*str)++; // Bronstein TC
1024             if(result = NextIntegerFromString( str, &temp2)) return -1;
1025             *inc = temp2 * 1000;
1026         } else *inc = 0;
1027         *moves = 0; *tc = temp * 1000; *incType = type;
1028         return 0;
1029     }
1030
1031     (*str)++; /* classical time control */
1032     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1033
1034     if(result == 0) {
1035         *moves = temp;
1036         *tc    = temp2 * 1000;
1037         *inc   = 0;
1038         *incType = type;
1039     }
1040     return result;
1041 }
1042
1043 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1044 {   /* [HGM] get time to add from the multi-session time-control string */
1045     int incType, moves=1; /* kludge to force reading of first session */
1046     long time, increment;
1047     char *s = tcString;
1048
1049     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1050     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1051     do {
1052         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1053         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1054         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1055         if(movenr == -1) return time;    /* last move before new session     */
1056         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1057         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1058         if(!moves) return increment;     /* current session is incremental   */
1059         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1060     } while(movenr >= -1);               /* try again for next session       */
1061
1062     return 0; // no new time quota on this move
1063 }
1064
1065 int
1066 ParseTimeControl(tc, ti, mps)
1067      char *tc;
1068      int ti;
1069      int mps;
1070 {
1071   long tc1;
1072   long tc2;
1073   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1074   int min, sec=0;
1075   int len;
1076
1077   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1078   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1079       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1080   if(ti > 0) {
1081
1082     if(mps)
1083       snprintf(buf, MSG_SIZ, ":%d/%s+%d", mps, mytc, ti);
1084     else 
1085       snprintf(buf, MSG_SIZ, ":%s+%d", mytc, ti);
1086   } else {
1087     if(mps)
1088       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1089     else 
1090       snprintf(buf, MSG_SIZ, ":%s", mytc);
1091   }
1092   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1093   
1094   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1095     return FALSE;
1096   }
1097
1098   if( *tc == '/' ) {
1099     /* Parse second time control */
1100     tc++;
1101
1102     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1103       return FALSE;
1104     }
1105
1106     if( tc2 == 0 ) {
1107       return FALSE;
1108     }
1109
1110     timeControl_2 = tc2 * 1000;
1111   }
1112   else {
1113     timeControl_2 = 0;
1114   }
1115
1116   if( tc1 == 0 ) {
1117     return FALSE;
1118   }
1119
1120   timeControl = tc1 * 1000;
1121
1122   if (ti >= 0) {
1123     timeIncrement = ti * 1000;  /* convert to ms */
1124     movesPerSession = 0;
1125   } else {
1126     timeIncrement = 0;
1127     movesPerSession = mps;
1128   }
1129   return TRUE;
1130 }
1131
1132 void
1133 InitBackEnd2()
1134 {
1135     if (appData.debugMode) {
1136         fprintf(debugFP, "%s\n", programVersion);
1137     }
1138
1139     set_cont_sequence(appData.wrapContSeq);
1140     if (appData.matchGames > 0) {
1141         appData.matchMode = TRUE;
1142     } else if (appData.matchMode) {
1143         appData.matchGames = 1;
1144     }
1145     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1146         appData.matchGames = appData.sameColorGames;
1147     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1148         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1149         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1150     }
1151     Reset(TRUE, FALSE);
1152     if (appData.noChessProgram || first.protocolVersion == 1) {
1153       InitBackEnd3();
1154     } else {
1155       /* kludge: allow timeout for initial "feature" commands */
1156       FreezeUI();
1157       DisplayMessage("", _("Starting chess program"));
1158       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1159     }
1160 }
1161
1162 void
1163 InitBackEnd3 P((void))
1164 {
1165     GameMode initialMode;
1166     char buf[MSG_SIZ];
1167     int err, len;
1168
1169     InitChessProgram(&first, startedFromSetupPosition);
1170
1171     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1172         free(programVersion);
1173         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1174         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1175     }
1176
1177     if (appData.icsActive) {
1178 #ifdef WIN32
1179         /* [DM] Make a console window if needed [HGM] merged ifs */
1180         ConsoleCreate();
1181 #endif
1182         err = establish();
1183         if (err != 0)
1184           {
1185             if (*appData.icsCommPort != NULLCHAR)
1186               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1187                              appData.icsCommPort);
1188             else
1189               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1190                         appData.icsHost, appData.icsPort);
1191
1192             if( (len > MSG_SIZ) && appData.debugMode )
1193               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1194
1195             DisplayFatalError(buf, err, 1);
1196             return;
1197         }
1198         SetICSMode();
1199         telnetISR =
1200           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1201         fromUserISR =
1202           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1203         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1204             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1205     } else if (appData.noChessProgram) {
1206         SetNCPMode();
1207     } else {
1208         SetGNUMode();
1209     }
1210
1211     if (*appData.cmailGameName != NULLCHAR) {
1212         SetCmailMode();
1213         OpenLoopback(&cmailPR);
1214         cmailISR =
1215           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1216     }
1217
1218     ThawUI();
1219     DisplayMessage("", "");
1220     if (StrCaseCmp(appData.initialMode, "") == 0) {
1221       initialMode = BeginningOfGame;
1222     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1223       initialMode = TwoMachinesPlay;
1224     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1225       initialMode = AnalyzeFile;
1226     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1227       initialMode = AnalyzeMode;
1228     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1229       initialMode = MachinePlaysWhite;
1230     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1231       initialMode = MachinePlaysBlack;
1232     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1233       initialMode = EditGame;
1234     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1235       initialMode = EditPosition;
1236     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1237       initialMode = Training;
1238     } else {
1239       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1240       if( (len > MSG_SIZ) && appData.debugMode )
1241         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1242
1243       DisplayFatalError(buf, 0, 2);
1244       return;
1245     }
1246
1247     if (appData.matchMode) {
1248         /* Set up machine vs. machine match */
1249         if (appData.noChessProgram) {
1250             DisplayFatalError(_("Can't have a match with no chess programs"),
1251                               0, 2);
1252             return;
1253         }
1254         matchMode = TRUE;
1255         matchGame = 1;
1256         if (*appData.loadGameFile != NULLCHAR) {
1257             int index = appData.loadGameIndex; // [HGM] autoinc
1258             if(index<0) lastIndex = index = 1;
1259             if (!LoadGameFromFile(appData.loadGameFile,
1260                                   index,
1261                                   appData.loadGameFile, FALSE)) {
1262                 DisplayFatalError(_("Bad game file"), 0, 1);
1263                 return;
1264             }
1265         } else if (*appData.loadPositionFile != NULLCHAR) {
1266             int index = appData.loadPositionIndex; // [HGM] autoinc
1267             if(index<0) lastIndex = index = 1;
1268             if (!LoadPositionFromFile(appData.loadPositionFile,
1269                                       index,
1270                                       appData.loadPositionFile)) {
1271                 DisplayFatalError(_("Bad position file"), 0, 1);
1272                 return;
1273             }
1274         }
1275         TwoMachinesEvent();
1276     } else if (*appData.cmailGameName != NULLCHAR) {
1277         /* Set up cmail mode */
1278         ReloadCmailMsgEvent(TRUE);
1279     } else {
1280         /* Set up other modes */
1281         if (initialMode == AnalyzeFile) {
1282           if (*appData.loadGameFile == NULLCHAR) {
1283             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1284             return;
1285           }
1286         }
1287         if (*appData.loadGameFile != NULLCHAR) {
1288             (void) LoadGameFromFile(appData.loadGameFile,
1289                                     appData.loadGameIndex,
1290                                     appData.loadGameFile, TRUE);
1291         } else if (*appData.loadPositionFile != NULLCHAR) {
1292             (void) LoadPositionFromFile(appData.loadPositionFile,
1293                                         appData.loadPositionIndex,
1294                                         appData.loadPositionFile);
1295             /* [HGM] try to make self-starting even after FEN load */
1296             /* to allow automatic setup of fairy variants with wtm */
1297             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1298                 gameMode = BeginningOfGame;
1299                 setboardSpoiledMachineBlack = 1;
1300             }
1301             /* [HGM] loadPos: make that every new game uses the setup */
1302             /* from file as long as we do not switch variant          */
1303             if(!blackPlaysFirst) {
1304                 startedFromPositionFile = TRUE;
1305                 CopyBoard(filePosition, boards[0]);
1306             }
1307         }
1308         if (initialMode == AnalyzeMode) {
1309           if (appData.noChessProgram) {
1310             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1311             return;
1312           }
1313           if (appData.icsActive) {
1314             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1315             return;
1316           }
1317           AnalyzeModeEvent();
1318         } else if (initialMode == AnalyzeFile) {
1319           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1320           ShowThinkingEvent();
1321           AnalyzeFileEvent();
1322           AnalysisPeriodicEvent(1);
1323         } else if (initialMode == MachinePlaysWhite) {
1324           if (appData.noChessProgram) {
1325             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1326                               0, 2);
1327             return;
1328           }
1329           if (appData.icsActive) {
1330             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1331                               0, 2);
1332             return;
1333           }
1334           MachineWhiteEvent();
1335         } else if (initialMode == MachinePlaysBlack) {
1336           if (appData.noChessProgram) {
1337             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1338                               0, 2);
1339             return;
1340           }
1341           if (appData.icsActive) {
1342             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1343                               0, 2);
1344             return;
1345           }
1346           MachineBlackEvent();
1347         } else if (initialMode == TwoMachinesPlay) {
1348           if (appData.noChessProgram) {
1349             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1350                               0, 2);
1351             return;
1352           }
1353           if (appData.icsActive) {
1354             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1355                               0, 2);
1356             return;
1357           }
1358           TwoMachinesEvent();
1359         } else if (initialMode == EditGame) {
1360           EditGameEvent();
1361         } else if (initialMode == EditPosition) {
1362           EditPositionEvent();
1363         } else if (initialMode == Training) {
1364           if (*appData.loadGameFile == NULLCHAR) {
1365             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1366             return;
1367           }
1368           TrainingEvent();
1369         }
1370     }
1371 }
1372
1373 /*
1374  * Establish will establish a contact to a remote host.port.
1375  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1376  *  used to talk to the host.
1377  * Returns 0 if okay, error code if not.
1378  */
1379 int
1380 establish()
1381 {
1382     char buf[MSG_SIZ];
1383
1384     if (*appData.icsCommPort != NULLCHAR) {
1385         /* Talk to the host through a serial comm port */
1386         return OpenCommPort(appData.icsCommPort, &icsPR);
1387
1388     } else if (*appData.gateway != NULLCHAR) {
1389         if (*appData.remoteShell == NULLCHAR) {
1390             /* Use the rcmd protocol to run telnet program on a gateway host */
1391             snprintf(buf, sizeof(buf), "%s %s %s",
1392                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1393             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1394
1395         } else {
1396             /* Use the rsh program to run telnet program on a gateway host */
1397             if (*appData.remoteUser == NULLCHAR) {
1398                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1399                         appData.gateway, appData.telnetProgram,
1400                         appData.icsHost, appData.icsPort);
1401             } else {
1402                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1403                         appData.remoteShell, appData.gateway,
1404                         appData.remoteUser, appData.telnetProgram,
1405                         appData.icsHost, appData.icsPort);
1406             }
1407             return StartChildProcess(buf, "", &icsPR);
1408
1409         }
1410     } else if (appData.useTelnet) {
1411         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1412
1413     } else {
1414         /* TCP socket interface differs somewhat between
1415            Unix and NT; handle details in the front end.
1416            */
1417         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1418     }
1419 }
1420
1421 void EscapeExpand(char *p, char *q)
1422 {       // [HGM] initstring: routine to shape up string arguments
1423         while(*p++ = *q++) if(p[-1] == '\\')
1424             switch(*q++) {
1425                 case 'n': p[-1] = '\n'; break;
1426                 case 'r': p[-1] = '\r'; break;
1427                 case 't': p[-1] = '\t'; break;
1428                 case '\\': p[-1] = '\\'; break;
1429                 case 0: *p = 0; return;
1430                 default: p[-1] = q[-1]; break;
1431             }
1432 }
1433
1434 void
1435 show_bytes(fp, buf, count)
1436      FILE *fp;
1437      char *buf;
1438      int count;
1439 {
1440     while (count--) {
1441         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1442             fprintf(fp, "\\%03o", *buf & 0xff);
1443         } else {
1444             putc(*buf, fp);
1445         }
1446         buf++;
1447     }
1448     fflush(fp);
1449 }
1450
1451 /* Returns an errno value */
1452 int
1453 OutputMaybeTelnet(pr, message, count, outError)
1454      ProcRef pr;
1455      char *message;
1456      int count;
1457      int *outError;
1458 {
1459     char buf[8192], *p, *q, *buflim;
1460     int left, newcount, outcount;
1461
1462     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1463         *appData.gateway != NULLCHAR) {
1464         if (appData.debugMode) {
1465             fprintf(debugFP, ">ICS: ");
1466             show_bytes(debugFP, message, count);
1467             fprintf(debugFP, "\n");
1468         }
1469         return OutputToProcess(pr, message, count, outError);
1470     }
1471
1472     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1473     p = message;
1474     q = buf;
1475     left = count;
1476     newcount = 0;
1477     while (left) {
1478         if (q >= buflim) {
1479             if (appData.debugMode) {
1480                 fprintf(debugFP, ">ICS: ");
1481                 show_bytes(debugFP, buf, newcount);
1482                 fprintf(debugFP, "\n");
1483             }
1484             outcount = OutputToProcess(pr, buf, newcount, outError);
1485             if (outcount < newcount) return -1; /* to be sure */
1486             q = buf;
1487             newcount = 0;
1488         }
1489         if (*p == '\n') {
1490             *q++ = '\r';
1491             newcount++;
1492         } else if (((unsigned char) *p) == TN_IAC) {
1493             *q++ = (char) TN_IAC;
1494             newcount ++;
1495         }
1496         *q++ = *p++;
1497         newcount++;
1498         left--;
1499     }
1500     if (appData.debugMode) {
1501         fprintf(debugFP, ">ICS: ");
1502         show_bytes(debugFP, buf, newcount);
1503         fprintf(debugFP, "\n");
1504     }
1505     outcount = OutputToProcess(pr, buf, newcount, outError);
1506     if (outcount < newcount) return -1; /* to be sure */
1507     return count;
1508 }
1509
1510 void
1511 read_from_player(isr, closure, message, count, error)
1512      InputSourceRef isr;
1513      VOIDSTAR closure;
1514      char *message;
1515      int count;
1516      int error;
1517 {
1518     int outError, outCount;
1519     static int gotEof = 0;
1520
1521     /* Pass data read from player on to ICS */
1522     if (count > 0) {
1523         gotEof = 0;
1524         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1525         if (outCount < count) {
1526             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1527         }
1528     } else if (count < 0) {
1529         RemoveInputSource(isr);
1530         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1531     } else if (gotEof++ > 0) {
1532         RemoveInputSource(isr);
1533         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1534     }
1535 }
1536
1537 void
1538 KeepAlive()
1539 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1540     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1541     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1542     SendToICS("date\n");
1543     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1544 }
1545
1546 /* added routine for printf style output to ics */
1547 void ics_printf(char *format, ...)
1548 {
1549     char buffer[MSG_SIZ];
1550     va_list args;
1551
1552     va_start(args, format);
1553     vsnprintf(buffer, sizeof(buffer), format, args);
1554     buffer[sizeof(buffer)-1] = '\0';
1555     SendToICS(buffer);
1556     va_end(args);
1557 }
1558
1559 void
1560 SendToICS(s)
1561      char *s;
1562 {
1563     int count, outCount, outError;
1564
1565     if (icsPR == NULL) return;
1566
1567     count = strlen(s);
1568     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1569     if (outCount < count) {
1570         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1571     }
1572 }
1573
1574 /* This is used for sending logon scripts to the ICS. Sending
1575    without a delay causes problems when using timestamp on ICC
1576    (at least on my machine). */
1577 void
1578 SendToICSDelayed(s,msdelay)
1579      char *s;
1580      long msdelay;
1581 {
1582     int count, outCount, outError;
1583
1584     if (icsPR == NULL) return;
1585
1586     count = strlen(s);
1587     if (appData.debugMode) {
1588         fprintf(debugFP, ">ICS: ");
1589         show_bytes(debugFP, s, count);
1590         fprintf(debugFP, "\n");
1591     }
1592     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1593                                       msdelay);
1594     if (outCount < count) {
1595         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1596     }
1597 }
1598
1599
1600 /* Remove all highlighting escape sequences in s
1601    Also deletes any suffix starting with '('
1602    */
1603 char *
1604 StripHighlightAndTitle(s)
1605      char *s;
1606 {
1607     static char retbuf[MSG_SIZ];
1608     char *p = retbuf;
1609
1610     while (*s != NULLCHAR) {
1611         while (*s == '\033') {
1612             while (*s != NULLCHAR && !isalpha(*s)) s++;
1613             if (*s != NULLCHAR) s++;
1614         }
1615         while (*s != NULLCHAR && *s != '\033') {
1616             if (*s == '(' || *s == '[') {
1617                 *p = NULLCHAR;
1618                 return retbuf;
1619             }
1620             *p++ = *s++;
1621         }
1622     }
1623     *p = NULLCHAR;
1624     return retbuf;
1625 }
1626
1627 /* Remove all highlighting escape sequences in s */
1628 char *
1629 StripHighlight(s)
1630      char *s;
1631 {
1632     static char retbuf[MSG_SIZ];
1633     char *p = retbuf;
1634
1635     while (*s != NULLCHAR) {
1636         while (*s == '\033') {
1637             while (*s != NULLCHAR && !isalpha(*s)) s++;
1638             if (*s != NULLCHAR) s++;
1639         }
1640         while (*s != NULLCHAR && *s != '\033') {
1641             *p++ = *s++;
1642         }
1643     }
1644     *p = NULLCHAR;
1645     return retbuf;
1646 }
1647
1648 char *variantNames[] = VARIANT_NAMES;
1649 char *
1650 VariantName(v)
1651      VariantClass v;
1652 {
1653     return variantNames[v];
1654 }
1655
1656
1657 /* Identify a variant from the strings the chess servers use or the
1658    PGN Variant tag names we use. */
1659 VariantClass
1660 StringToVariant(e)
1661      char *e;
1662 {
1663     char *p;
1664     int wnum = -1;
1665     VariantClass v = VariantNormal;
1666     int i, found = FALSE;
1667     char buf[MSG_SIZ];
1668     int len;
1669
1670     if (!e) return v;
1671
1672     /* [HGM] skip over optional board-size prefixes */
1673     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1674         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1675         while( *e++ != '_');
1676     }
1677
1678     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1679         v = VariantNormal;
1680         found = TRUE;
1681     } else
1682     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1683       if (StrCaseStr(e, variantNames[i])) {
1684         v = (VariantClass) i;
1685         found = TRUE;
1686         break;
1687       }
1688     }
1689
1690     if (!found) {
1691       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1692           || StrCaseStr(e, "wild/fr")
1693           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1694         v = VariantFischeRandom;
1695       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1696                  (i = 1, p = StrCaseStr(e, "w"))) {
1697         p += i;
1698         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1699         if (isdigit(*p)) {
1700           wnum = atoi(p);
1701         } else {
1702           wnum = -1;
1703         }
1704         switch (wnum) {
1705         case 0: /* FICS only, actually */
1706         case 1:
1707           /* Castling legal even if K starts on d-file */
1708           v = VariantWildCastle;
1709           break;
1710         case 2:
1711         case 3:
1712         case 4:
1713           /* Castling illegal even if K & R happen to start in
1714              normal positions. */
1715           v = VariantNoCastle;
1716           break;
1717         case 5:
1718         case 7:
1719         case 8:
1720         case 10:
1721         case 11:
1722         case 12:
1723         case 13:
1724         case 14:
1725         case 15:
1726         case 18:
1727         case 19:
1728           /* Castling legal iff K & R start in normal positions */
1729           v = VariantNormal;
1730           break;
1731         case 6:
1732         case 20:
1733         case 21:
1734           /* Special wilds for position setup; unclear what to do here */
1735           v = VariantLoadable;
1736           break;
1737         case 9:
1738           /* Bizarre ICC game */
1739           v = VariantTwoKings;
1740           break;
1741         case 16:
1742           v = VariantKriegspiel;
1743           break;
1744         case 17:
1745           v = VariantLosers;
1746           break;
1747         case 22:
1748           v = VariantFischeRandom;
1749           break;
1750         case 23:
1751           v = VariantCrazyhouse;
1752           break;
1753         case 24:
1754           v = VariantBughouse;
1755           break;
1756         case 25:
1757           v = Variant3Check;
1758           break;
1759         case 26:
1760           /* Not quite the same as FICS suicide! */
1761           v = VariantGiveaway;
1762           break;
1763         case 27:
1764           v = VariantAtomic;
1765           break;
1766         case 28:
1767           v = VariantShatranj;
1768           break;
1769
1770         /* Temporary names for future ICC types.  The name *will* change in
1771            the next xboard/WinBoard release after ICC defines it. */
1772         case 29:
1773           v = Variant29;
1774           break;
1775         case 30:
1776           v = Variant30;
1777           break;
1778         case 31:
1779           v = Variant31;
1780           break;
1781         case 32:
1782           v = Variant32;
1783           break;
1784         case 33:
1785           v = Variant33;
1786           break;
1787         case 34:
1788           v = Variant34;
1789           break;
1790         case 35:
1791           v = Variant35;
1792           break;
1793         case 36:
1794           v = Variant36;
1795           break;
1796         case 37:
1797           v = VariantShogi;
1798           break;
1799         case 38:
1800           v = VariantXiangqi;
1801           break;
1802         case 39:
1803           v = VariantCourier;
1804           break;
1805         case 40:
1806           v = VariantGothic;
1807           break;
1808         case 41:
1809           v = VariantCapablanca;
1810           break;
1811         case 42:
1812           v = VariantKnightmate;
1813           break;
1814         case 43:
1815           v = VariantFairy;
1816           break;
1817         case 44:
1818           v = VariantCylinder;
1819           break;
1820         case 45:
1821           v = VariantFalcon;
1822           break;
1823         case 46:
1824           v = VariantCapaRandom;
1825           break;
1826         case 47:
1827           v = VariantBerolina;
1828           break;
1829         case 48:
1830           v = VariantJanus;
1831           break;
1832         case 49:
1833           v = VariantSuper;
1834           break;
1835         case 50:
1836           v = VariantGreat;
1837           break;
1838         case -1:
1839           /* Found "wild" or "w" in the string but no number;
1840              must assume it's normal chess. */
1841           v = VariantNormal;
1842           break;
1843         default:
1844           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1845           if( (len > MSG_SIZ) && appData.debugMode )
1846             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1847
1848           DisplayError(buf, 0);
1849           v = VariantUnknown;
1850           break;
1851         }
1852       }
1853     }
1854     if (appData.debugMode) {
1855       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1856               e, wnum, VariantName(v));
1857     }
1858     return v;
1859 }
1860
1861 static int leftover_start = 0, leftover_len = 0;
1862 char star_match[STAR_MATCH_N][MSG_SIZ];
1863
1864 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1865    advance *index beyond it, and set leftover_start to the new value of
1866    *index; else return FALSE.  If pattern contains the character '*', it
1867    matches any sequence of characters not containing '\r', '\n', or the
1868    character following the '*' (if any), and the matched sequence(s) are
1869    copied into star_match.
1870    */
1871 int
1872 looking_at(buf, index, pattern)
1873      char *buf;
1874      int *index;
1875      char *pattern;
1876 {
1877     char *bufp = &buf[*index], *patternp = pattern;
1878     int star_count = 0;
1879     char *matchp = star_match[0];
1880
1881     for (;;) {
1882         if (*patternp == NULLCHAR) {
1883             *index = leftover_start = bufp - buf;
1884             *matchp = NULLCHAR;
1885             return TRUE;
1886         }
1887         if (*bufp == NULLCHAR) return FALSE;
1888         if (*patternp == '*') {
1889             if (*bufp == *(patternp + 1)) {
1890                 *matchp = NULLCHAR;
1891                 matchp = star_match[++star_count];
1892                 patternp += 2;
1893                 bufp++;
1894                 continue;
1895             } else if (*bufp == '\n' || *bufp == '\r') {
1896                 patternp++;
1897                 if (*patternp == NULLCHAR)
1898                   continue;
1899                 else
1900                   return FALSE;
1901             } else {
1902                 *matchp++ = *bufp++;
1903                 continue;
1904             }
1905         }
1906         if (*patternp != *bufp) return FALSE;
1907         patternp++;
1908         bufp++;
1909     }
1910 }
1911
1912 void
1913 SendToPlayer(data, length)
1914      char *data;
1915      int length;
1916 {
1917     int error, outCount;
1918     outCount = OutputToProcess(NoProc, data, length, &error);
1919     if (outCount < length) {
1920         DisplayFatalError(_("Error writing to display"), error, 1);
1921     }
1922 }
1923
1924 void
1925 PackHolding(packed, holding)
1926      char packed[];
1927      char *holding;
1928 {
1929     char *p = holding;
1930     char *q = packed;
1931     int runlength = 0;
1932     int curr = 9999;
1933     do {
1934         if (*p == curr) {
1935             runlength++;
1936         } else {
1937             switch (runlength) {
1938               case 0:
1939                 break;
1940               case 1:
1941                 *q++ = curr;
1942                 break;
1943               case 2:
1944                 *q++ = curr;
1945                 *q++ = curr;
1946                 break;
1947               default:
1948                 sprintf(q, "%d", runlength);
1949                 while (*q) q++;
1950                 *q++ = curr;
1951                 break;
1952             }
1953             runlength = 1;
1954             curr = *p;
1955         }
1956     } while (*p++);
1957     *q = NULLCHAR;
1958 }
1959
1960 /* Telnet protocol requests from the front end */
1961 void
1962 TelnetRequest(ddww, option)
1963      unsigned char ddww, option;
1964 {
1965     unsigned char msg[3];
1966     int outCount, outError;
1967
1968     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1969
1970     if (appData.debugMode) {
1971         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1972         switch (ddww) {
1973           case TN_DO:
1974             ddwwStr = "DO";
1975             break;
1976           case TN_DONT:
1977             ddwwStr = "DONT";
1978             break;
1979           case TN_WILL:
1980             ddwwStr = "WILL";
1981             break;
1982           case TN_WONT:
1983             ddwwStr = "WONT";
1984             break;
1985           default:
1986             ddwwStr = buf1;
1987             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1988             break;
1989         }
1990         switch (option) {
1991           case TN_ECHO:
1992             optionStr = "ECHO";
1993             break;
1994           default:
1995             optionStr = buf2;
1996             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
1997             break;
1998         }
1999         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2000     }
2001     msg[0] = TN_IAC;
2002     msg[1] = ddww;
2003     msg[2] = option;
2004     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2005     if (outCount < 3) {
2006         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2007     }
2008 }
2009
2010 void
2011 DoEcho()
2012 {
2013     if (!appData.icsActive) return;
2014     TelnetRequest(TN_DO, TN_ECHO);
2015 }
2016
2017 void
2018 DontEcho()
2019 {
2020     if (!appData.icsActive) return;
2021     TelnetRequest(TN_DONT, TN_ECHO);
2022 }
2023
2024 void
2025 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2026 {
2027     /* put the holdings sent to us by the server on the board holdings area */
2028     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2029     char p;
2030     ChessSquare piece;
2031
2032     if(gameInfo.holdingsWidth < 2)  return;
2033     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2034         return; // prevent overwriting by pre-board holdings
2035
2036     if( (int)lowestPiece >= BlackPawn ) {
2037         holdingsColumn = 0;
2038         countsColumn = 1;
2039         holdingsStartRow = BOARD_HEIGHT-1;
2040         direction = -1;
2041     } else {
2042         holdingsColumn = BOARD_WIDTH-1;
2043         countsColumn = BOARD_WIDTH-2;
2044         holdingsStartRow = 0;
2045         direction = 1;
2046     }
2047
2048     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2049         board[i][holdingsColumn] = EmptySquare;
2050         board[i][countsColumn]   = (ChessSquare) 0;
2051     }
2052     while( (p=*holdings++) != NULLCHAR ) {
2053         piece = CharToPiece( ToUpper(p) );
2054         if(piece == EmptySquare) continue;
2055         /*j = (int) piece - (int) WhitePawn;*/
2056         j = PieceToNumber(piece);
2057         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2058         if(j < 0) continue;               /* should not happen */
2059         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2060         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2061         board[holdingsStartRow+j*direction][countsColumn]++;
2062     }
2063 }
2064
2065
2066 void
2067 VariantSwitch(Board board, VariantClass newVariant)
2068 {
2069    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2070    static Board oldBoard;
2071
2072    startedFromPositionFile = FALSE;
2073    if(gameInfo.variant == newVariant) return;
2074
2075    /* [HGM] This routine is called each time an assignment is made to
2076     * gameInfo.variant during a game, to make sure the board sizes
2077     * are set to match the new variant. If that means adding or deleting
2078     * holdings, we shift the playing board accordingly
2079     * This kludge is needed because in ICS observe mode, we get boards
2080     * of an ongoing game without knowing the variant, and learn about the
2081     * latter only later. This can be because of the move list we requested,
2082     * in which case the game history is refilled from the beginning anyway,
2083     * but also when receiving holdings of a crazyhouse game. In the latter
2084     * case we want to add those holdings to the already received position.
2085     */
2086
2087
2088    if (appData.debugMode) {
2089      fprintf(debugFP, "Switch board from %s to %s\n",
2090              VariantName(gameInfo.variant), VariantName(newVariant));
2091      setbuf(debugFP, NULL);
2092    }
2093    shuffleOpenings = 0;       /* [HGM] shuffle */
2094    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2095    switch(newVariant)
2096      {
2097      case VariantShogi:
2098        newWidth = 9;  newHeight = 9;
2099        gameInfo.holdingsSize = 7;
2100      case VariantBughouse:
2101      case VariantCrazyhouse:
2102        newHoldingsWidth = 2; break;
2103      case VariantGreat:
2104        newWidth = 10;
2105      case VariantSuper:
2106        newHoldingsWidth = 2;
2107        gameInfo.holdingsSize = 8;
2108        break;
2109      case VariantGothic:
2110      case VariantCapablanca:
2111      case VariantCapaRandom:
2112        newWidth = 10;
2113      default:
2114        newHoldingsWidth = gameInfo.holdingsSize = 0;
2115      };
2116
2117    if(newWidth  != gameInfo.boardWidth  ||
2118       newHeight != gameInfo.boardHeight ||
2119       newHoldingsWidth != gameInfo.holdingsWidth ) {
2120
2121      /* shift position to new playing area, if needed */
2122      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2123        for(i=0; i<BOARD_HEIGHT; i++)
2124          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2125            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2126              board[i][j];
2127        for(i=0; i<newHeight; i++) {
2128          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2129          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2130        }
2131      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2132        for(i=0; i<BOARD_HEIGHT; i++)
2133          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2134            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2135              board[i][j];
2136      }
2137      gameInfo.boardWidth  = newWidth;
2138      gameInfo.boardHeight = newHeight;
2139      gameInfo.holdingsWidth = newHoldingsWidth;
2140      gameInfo.variant = newVariant;
2141      InitDrawingSizes(-2, 0);
2142    } else gameInfo.variant = newVariant;
2143    CopyBoard(oldBoard, board);   // remember correctly formatted board
2144      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2145    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2146 }
2147
2148 static int loggedOn = FALSE;
2149
2150 /*-- Game start info cache: --*/
2151 int gs_gamenum;
2152 char gs_kind[MSG_SIZ];
2153 static char player1Name[128] = "";
2154 static char player2Name[128] = "";
2155 static char cont_seq[] = "\n\\   ";
2156 static int player1Rating = -1;
2157 static int player2Rating = -1;
2158 /*----------------------------*/
2159
2160 ColorClass curColor = ColorNormal;
2161 int suppressKibitz = 0;
2162
2163 // [HGM] seekgraph
2164 Boolean soughtPending = FALSE;
2165 Boolean seekGraphUp;
2166 #define MAX_SEEK_ADS 200
2167 #define SQUARE 0x80
2168 char *seekAdList[MAX_SEEK_ADS];
2169 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2170 float tcList[MAX_SEEK_ADS];
2171 char colorList[MAX_SEEK_ADS];
2172 int nrOfSeekAds = 0;
2173 int minRating = 1010, maxRating = 2800;
2174 int hMargin = 10, vMargin = 20, h, w;
2175 extern int squareSize, lineGap;
2176
2177 void
2178 PlotSeekAd(int i)
2179 {
2180         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2181         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2182         if(r < minRating+100 && r >=0 ) r = minRating+100;
2183         if(r > maxRating) r = maxRating;
2184         if(tc < 1.) tc = 1.;
2185         if(tc > 95.) tc = 95.;
2186         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2187         y = ((double)r - minRating)/(maxRating - minRating)
2188             * (h-vMargin-squareSize/8-1) + vMargin;
2189         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2190         if(strstr(seekAdList[i], " u ")) color = 1;
2191         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2192            !strstr(seekAdList[i], "bullet") &&
2193            !strstr(seekAdList[i], "blitz") &&
2194            !strstr(seekAdList[i], "standard") ) color = 2;
2195         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2196         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2197 }
2198
2199 void
2200 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2201 {
2202         char buf[MSG_SIZ], *ext = "";
2203         VariantClass v = StringToVariant(type);
2204         if(strstr(type, "wild")) {
2205             ext = type + 4; // append wild number
2206             if(v == VariantFischeRandom) type = "chess960"; else
2207             if(v == VariantLoadable) type = "setup"; else
2208             type = VariantName(v);
2209         }
2210         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2211         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2212             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2213             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2214             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2215             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2216             seekNrList[nrOfSeekAds] = nr;
2217             zList[nrOfSeekAds] = 0;
2218             seekAdList[nrOfSeekAds++] = StrSave(buf);
2219             if(plot) PlotSeekAd(nrOfSeekAds-1);
2220         }
2221 }
2222
2223 void
2224 EraseSeekDot(int i)
2225 {
2226     int x = xList[i], y = yList[i], d=squareSize/4, k;
2227     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2228     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2229     // now replot every dot that overlapped
2230     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2231         int xx = xList[k], yy = yList[k];
2232         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2233             DrawSeekDot(xx, yy, colorList[k]);
2234     }
2235 }
2236
2237 void
2238 RemoveSeekAd(int nr)
2239 {
2240         int i;
2241         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2242             EraseSeekDot(i);
2243             if(seekAdList[i]) free(seekAdList[i]);
2244             seekAdList[i] = seekAdList[--nrOfSeekAds];
2245             seekNrList[i] = seekNrList[nrOfSeekAds];
2246             ratingList[i] = ratingList[nrOfSeekAds];
2247             colorList[i]  = colorList[nrOfSeekAds];
2248             tcList[i] = tcList[nrOfSeekAds];
2249             xList[i]  = xList[nrOfSeekAds];
2250             yList[i]  = yList[nrOfSeekAds];
2251             zList[i]  = zList[nrOfSeekAds];
2252             seekAdList[nrOfSeekAds] = NULL;
2253             break;
2254         }
2255 }
2256
2257 Boolean
2258 MatchSoughtLine(char *line)
2259 {
2260     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2261     int nr, base, inc, u=0; char dummy;
2262
2263     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2264        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2265        (u=1) &&
2266        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2267         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2268         // match: compact and save the line
2269         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2270         return TRUE;
2271     }
2272     return FALSE;
2273 }
2274
2275 int
2276 DrawSeekGraph()
2277 {
2278     int i;
2279     if(!seekGraphUp) return FALSE;
2280     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2281     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2282
2283     DrawSeekBackground(0, 0, w, h);
2284     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2285     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2286     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2287         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2288         yy = h-1-yy;
2289         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2290         if(i%500 == 0) {
2291             char buf[MSG_SIZ];
2292             snprintf(buf, MSG_SIZ, "%d", i);
2293             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2294         }
2295     }
2296     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2297     for(i=1; i<100; i+=(i<10?1:5)) {
2298         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2299         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2300         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2301             char buf[MSG_SIZ];
2302             snprintf(buf, MSG_SIZ, "%d", i);
2303             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2304         }
2305     }
2306     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2307     return TRUE;
2308 }
2309
2310 int SeekGraphClick(ClickType click, int x, int y, int moving)
2311 {
2312     static int lastDown = 0, displayed = 0, lastSecond;
2313     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2314         if(click == Release || moving) return FALSE;
2315         nrOfSeekAds = 0;
2316         soughtPending = TRUE;
2317         SendToICS(ics_prefix);
2318         SendToICS("sought\n"); // should this be "sought all"?
2319     } else { // issue challenge based on clicked ad
2320         int dist = 10000; int i, closest = 0, second = 0;
2321         for(i=0; i<nrOfSeekAds; i++) {
2322             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2323             if(d < dist) { dist = d; closest = i; }
2324             second += (d - zList[i] < 120); // count in-range ads
2325             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2326         }
2327         if(dist < 120) {
2328             char buf[MSG_SIZ];
2329             second = (second > 1);
2330             if(displayed != closest || second != lastSecond) {
2331                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2332                 lastSecond = second; displayed = closest;
2333             }
2334             if(click == Press) {
2335                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2336                 lastDown = closest;
2337                 return TRUE;
2338             } // on press 'hit', only show info
2339             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2340             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2341             SendToICS(ics_prefix);
2342             SendToICS(buf);
2343             return TRUE; // let incoming board of started game pop down the graph
2344         } else if(click == Release) { // release 'miss' is ignored
2345             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2346             if(moving == 2) { // right up-click
2347                 nrOfSeekAds = 0; // refresh graph
2348                 soughtPending = TRUE;
2349                 SendToICS(ics_prefix);
2350                 SendToICS("sought\n"); // should this be "sought all"?
2351             }
2352             return TRUE;
2353         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2354         // press miss or release hit 'pop down' seek graph
2355         seekGraphUp = FALSE;
2356         DrawPosition(TRUE, NULL);
2357     }
2358     return TRUE;
2359 }
2360
2361 void
2362 read_from_ics(isr, closure, data, count, error)
2363      InputSourceRef isr;
2364      VOIDSTAR closure;
2365      char *data;
2366      int count;
2367      int error;
2368 {
2369 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2370 #define STARTED_NONE 0
2371 #define STARTED_MOVES 1
2372 #define STARTED_BOARD 2
2373 #define STARTED_OBSERVE 3
2374 #define STARTED_HOLDINGS 4
2375 #define STARTED_CHATTER 5
2376 #define STARTED_COMMENT 6
2377 #define STARTED_MOVES_NOHIDE 7
2378
2379     static int started = STARTED_NONE;
2380     static char parse[20000];
2381     static int parse_pos = 0;
2382     static char buf[BUF_SIZE + 1];
2383     static int firstTime = TRUE, intfSet = FALSE;
2384     static ColorClass prevColor = ColorNormal;
2385     static int savingComment = FALSE;
2386     static int cmatch = 0; // continuation sequence match
2387     char *bp;
2388     char str[MSG_SIZ];
2389     int i, oldi;
2390     int buf_len;
2391     int next_out;
2392     int tkind;
2393     int backup;    /* [DM] For zippy color lines */
2394     char *p;
2395     char talker[MSG_SIZ]; // [HGM] chat
2396     int channel;
2397
2398     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2399
2400     if (appData.debugMode) {
2401       if (!error) {
2402         fprintf(debugFP, "<ICS: ");
2403         show_bytes(debugFP, data, count);
2404         fprintf(debugFP, "\n");
2405       }
2406     }
2407
2408     if (appData.debugMode) { int f = forwardMostMove;
2409         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2410                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2411                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2412     }
2413     if (count > 0) {
2414         /* If last read ended with a partial line that we couldn't parse,
2415            prepend it to the new read and try again. */
2416         if (leftover_len > 0) {
2417             for (i=0; i<leftover_len; i++)
2418               buf[i] = buf[leftover_start + i];
2419         }
2420
2421     /* copy new characters into the buffer */
2422     bp = buf + leftover_len;
2423     buf_len=leftover_len;
2424     for (i=0; i<count; i++)
2425     {
2426         // ignore these
2427         if (data[i] == '\r')
2428             continue;
2429
2430         // join lines split by ICS?
2431         if (!appData.noJoin)
2432         {
2433             /*
2434                 Joining just consists of finding matches against the
2435                 continuation sequence, and discarding that sequence
2436                 if found instead of copying it.  So, until a match
2437                 fails, there's nothing to do since it might be the
2438                 complete sequence, and thus, something we don't want
2439                 copied.
2440             */
2441             if (data[i] == cont_seq[cmatch])
2442             {
2443                 cmatch++;
2444                 if (cmatch == strlen(cont_seq))
2445                 {
2446                     cmatch = 0; // complete match.  just reset the counter
2447
2448                     /*
2449                         it's possible for the ICS to not include the space
2450                         at the end of the last word, making our [correct]
2451                         join operation fuse two separate words.  the server
2452                         does this when the space occurs at the width setting.
2453                     */
2454                     if (!buf_len || buf[buf_len-1] != ' ')
2455                     {
2456                         *bp++ = ' ';
2457                         buf_len++;
2458                     }
2459                 }
2460                 continue;
2461             }
2462             else if (cmatch)
2463             {
2464                 /*
2465                     match failed, so we have to copy what matched before
2466                     falling through and copying this character.  In reality,
2467                     this will only ever be just the newline character, but
2468                     it doesn't hurt to be precise.
2469                 */
2470                 strncpy(bp, cont_seq, cmatch);
2471                 bp += cmatch;
2472                 buf_len += cmatch;
2473                 cmatch = 0;
2474             }
2475         }
2476
2477         // copy this char
2478         *bp++ = data[i];
2479         buf_len++;
2480     }
2481
2482         buf[buf_len] = NULLCHAR;
2483 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2484         next_out = 0;
2485         leftover_start = 0;
2486
2487         i = 0;
2488         while (i < buf_len) {
2489             /* Deal with part of the TELNET option negotiation
2490                protocol.  We refuse to do anything beyond the
2491                defaults, except that we allow the WILL ECHO option,
2492                which ICS uses to turn off password echoing when we are
2493                directly connected to it.  We reject this option
2494                if localLineEditing mode is on (always on in xboard)
2495                and we are talking to port 23, which might be a real
2496                telnet server that will try to keep WILL ECHO on permanently.
2497              */
2498             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2499                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2500                 unsigned char option;
2501                 oldi = i;
2502                 switch ((unsigned char) buf[++i]) {
2503                   case TN_WILL:
2504                     if (appData.debugMode)
2505                       fprintf(debugFP, "\n<WILL ");
2506                     switch (option = (unsigned char) buf[++i]) {
2507                       case TN_ECHO:
2508                         if (appData.debugMode)
2509                           fprintf(debugFP, "ECHO ");
2510                         /* Reply only if this is a change, according
2511                            to the protocol rules. */
2512                         if (remoteEchoOption) break;
2513                         if (appData.localLineEditing &&
2514                             atoi(appData.icsPort) == TN_PORT) {
2515                             TelnetRequest(TN_DONT, TN_ECHO);
2516                         } else {
2517                             EchoOff();
2518                             TelnetRequest(TN_DO, TN_ECHO);
2519                             remoteEchoOption = TRUE;
2520                         }
2521                         break;
2522                       default:
2523                         if (appData.debugMode)
2524                           fprintf(debugFP, "%d ", option);
2525                         /* Whatever this is, we don't want it. */
2526                         TelnetRequest(TN_DONT, option);
2527                         break;
2528                     }
2529                     break;
2530                   case TN_WONT:
2531                     if (appData.debugMode)
2532                       fprintf(debugFP, "\n<WONT ");
2533                     switch (option = (unsigned char) buf[++i]) {
2534                       case TN_ECHO:
2535                         if (appData.debugMode)
2536                           fprintf(debugFP, "ECHO ");
2537                         /* Reply only if this is a change, according
2538                            to the protocol rules. */
2539                         if (!remoteEchoOption) break;
2540                         EchoOn();
2541                         TelnetRequest(TN_DONT, TN_ECHO);
2542                         remoteEchoOption = FALSE;
2543                         break;
2544                       default:
2545                         if (appData.debugMode)
2546                           fprintf(debugFP, "%d ", (unsigned char) option);
2547                         /* Whatever this is, it must already be turned
2548                            off, because we never agree to turn on
2549                            anything non-default, so according to the
2550                            protocol rules, we don't reply. */
2551                         break;
2552                     }
2553                     break;
2554                   case TN_DO:
2555                     if (appData.debugMode)
2556                       fprintf(debugFP, "\n<DO ");
2557                     switch (option = (unsigned char) buf[++i]) {
2558                       default:
2559                         /* Whatever this is, we refuse to do it. */
2560                         if (appData.debugMode)
2561                           fprintf(debugFP, "%d ", option);
2562                         TelnetRequest(TN_WONT, option);
2563                         break;
2564                     }
2565                     break;
2566                   case TN_DONT:
2567                     if (appData.debugMode)
2568                       fprintf(debugFP, "\n<DONT ");
2569                     switch (option = (unsigned char) buf[++i]) {
2570                       default:
2571                         if (appData.debugMode)
2572                           fprintf(debugFP, "%d ", option);
2573                         /* Whatever this is, we are already not doing
2574                            it, because we never agree to do anything
2575                            non-default, so according to the protocol
2576                            rules, we don't reply. */
2577                         break;
2578                     }
2579                     break;
2580                   case TN_IAC:
2581                     if (appData.debugMode)
2582                       fprintf(debugFP, "\n<IAC ");
2583                     /* Doubled IAC; pass it through */
2584                     i--;
2585                     break;
2586                   default:
2587                     if (appData.debugMode)
2588                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2589                     /* Drop all other telnet commands on the floor */
2590                     break;
2591                 }
2592                 if (oldi > next_out)
2593                   SendToPlayer(&buf[next_out], oldi - next_out);
2594                 if (++i > next_out)
2595                   next_out = i;
2596                 continue;
2597             }
2598
2599             /* OK, this at least will *usually* work */
2600             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2601                 loggedOn = TRUE;
2602             }
2603
2604             if (loggedOn && !intfSet) {
2605                 if (ics_type == ICS_ICC) {
2606                   snprintf(str, MSG_SIZ,
2607                           "/set-quietly interface %s\n/set-quietly style 12\n",
2608                           programVersion);
2609                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2610                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2611                 } else if (ics_type == ICS_CHESSNET) {
2612                   snprintf(str, MSG_SIZ, "/style 12\n");
2613                 } else {
2614                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2615                   strcat(str, programVersion);
2616                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2617                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2618                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2619 #ifdef WIN32
2620                   strcat(str, "$iset nohighlight 1\n");
2621 #endif
2622                   strcat(str, "$iset lock 1\n$style 12\n");
2623                 }
2624                 SendToICS(str);
2625                 NotifyFrontendLogin();
2626                 intfSet = TRUE;
2627             }
2628
2629             if (started == STARTED_COMMENT) {
2630                 /* Accumulate characters in comment */
2631                 parse[parse_pos++] = buf[i];
2632                 if (buf[i] == '\n') {
2633                     parse[parse_pos] = NULLCHAR;
2634                     if(chattingPartner>=0) {
2635                         char mess[MSG_SIZ];
2636                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2637                         OutputChatMessage(chattingPartner, mess);
2638                         chattingPartner = -1;
2639                         next_out = i+1; // [HGM] suppress printing in ICS window
2640                     } else
2641                     if(!suppressKibitz) // [HGM] kibitz
2642                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2643                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2644                         int nrDigit = 0, nrAlph = 0, j;
2645                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2646                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2647                         parse[parse_pos] = NULLCHAR;
2648                         // try to be smart: if it does not look like search info, it should go to
2649                         // ICS interaction window after all, not to engine-output window.
2650                         for(j=0; j<parse_pos; j++) { // count letters and digits
2651                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2652                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2653                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2654                         }
2655                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2656                             int depth=0; float score;
2657                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2658                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2659                                 pvInfoList[forwardMostMove-1].depth = depth;
2660                                 pvInfoList[forwardMostMove-1].score = 100*score;
2661                             }
2662                             OutputKibitz(suppressKibitz, parse);
2663                         } else {
2664                             char tmp[MSG_SIZ];
2665                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2666                             SendToPlayer(tmp, strlen(tmp));
2667                         }
2668                         next_out = i+1; // [HGM] suppress printing in ICS window
2669                     }
2670                     started = STARTED_NONE;
2671                 } else {
2672                     /* Don't match patterns against characters in comment */
2673                     i++;
2674                     continue;
2675                 }
2676             }
2677             if (started == STARTED_CHATTER) {
2678                 if (buf[i] != '\n') {
2679                     /* Don't match patterns against characters in chatter */
2680                     i++;
2681                     continue;
2682                 }
2683                 started = STARTED_NONE;
2684                 if(suppressKibitz) next_out = i+1;
2685             }
2686
2687             /* Kludge to deal with rcmd protocol */
2688             if (firstTime && looking_at(buf, &i, "\001*")) {
2689                 DisplayFatalError(&buf[1], 0, 1);
2690                 continue;
2691             } else {
2692                 firstTime = FALSE;
2693             }
2694
2695             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2696                 ics_type = ICS_ICC;
2697                 ics_prefix = "/";
2698                 if (appData.debugMode)
2699                   fprintf(debugFP, "ics_type %d\n", ics_type);
2700                 continue;
2701             }
2702             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2703                 ics_type = ICS_FICS;
2704                 ics_prefix = "$";
2705                 if (appData.debugMode)
2706                   fprintf(debugFP, "ics_type %d\n", ics_type);
2707                 continue;
2708             }
2709             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2710                 ics_type = ICS_CHESSNET;
2711                 ics_prefix = "/";
2712                 if (appData.debugMode)
2713                   fprintf(debugFP, "ics_type %d\n", ics_type);
2714                 continue;
2715             }
2716
2717             if (!loggedOn &&
2718                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2719                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2720                  looking_at(buf, &i, "will be \"*\""))) {
2721               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2722               continue;
2723             }
2724
2725             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2726               char buf[MSG_SIZ];
2727               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2728               DisplayIcsInteractionTitle(buf);
2729               have_set_title = TRUE;
2730             }
2731
2732             /* skip finger notes */
2733             if (started == STARTED_NONE &&
2734                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2735                  (buf[i] == '1' && buf[i+1] == '0')) &&
2736                 buf[i+2] == ':' && buf[i+3] == ' ') {
2737               started = STARTED_CHATTER;
2738               i += 3;
2739               continue;
2740             }
2741
2742             oldi = i;
2743             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2744             if(appData.seekGraph) {
2745                 if(soughtPending && MatchSoughtLine(buf+i)) {
2746                     i = strstr(buf+i, "rated") - buf;
2747                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2748                     next_out = leftover_start = i;
2749                     started = STARTED_CHATTER;
2750                     suppressKibitz = TRUE;
2751                     continue;
2752                 }
2753                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2754                         && looking_at(buf, &i, "* ads displayed")) {
2755                     soughtPending = FALSE;
2756                     seekGraphUp = TRUE;
2757                     DrawSeekGraph();
2758                     continue;
2759                 }
2760                 if(appData.autoRefresh) {
2761                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2762                         int s = (ics_type == ICS_ICC); // ICC format differs
2763                         if(seekGraphUp)
2764                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2765                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2766                         looking_at(buf, &i, "*% "); // eat prompt
2767                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2768                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2769                         next_out = i; // suppress
2770                         continue;
2771                     }
2772                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2773                         char *p = star_match[0];
2774                         while(*p) {
2775                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2776                             while(*p && *p++ != ' '); // next
2777                         }
2778                         looking_at(buf, &i, "*% "); // eat prompt
2779                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2780                         next_out = i;
2781                         continue;
2782                     }
2783                 }
2784             }
2785
2786             /* skip formula vars */
2787             if (started == STARTED_NONE &&
2788                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2789               started = STARTED_CHATTER;
2790               i += 3;
2791               continue;
2792             }
2793
2794             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2795             if (appData.autoKibitz && started == STARTED_NONE &&
2796                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2797                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2798                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2799                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2800                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2801                         suppressKibitz = TRUE;
2802                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2803                         next_out = i;
2804                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2805                                 && (gameMode == IcsPlayingWhite)) ||
2806                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2807                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2808                             started = STARTED_CHATTER; // own kibitz we simply discard
2809                         else {
2810                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2811                             parse_pos = 0; parse[0] = NULLCHAR;
2812                             savingComment = TRUE;
2813                             suppressKibitz = gameMode != IcsObserving ? 2 :
2814                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2815                         }
2816                         continue;
2817                 } else
2818                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2819                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2820                          && atoi(star_match[0])) {
2821                     // suppress the acknowledgements of our own autoKibitz
2822                     char *p;
2823                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2824                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2825                     SendToPlayer(star_match[0], strlen(star_match[0]));
2826                     if(looking_at(buf, &i, "*% ")) // eat prompt
2827                         suppressKibitz = FALSE;
2828                     next_out = i;
2829                     continue;
2830                 }
2831             } // [HGM] kibitz: end of patch
2832
2833             // [HGM] chat: intercept tells by users for which we have an open chat window
2834             channel = -1;
2835             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2836                                            looking_at(buf, &i, "* whispers:") ||
2837                                            looking_at(buf, &i, "* kibitzes:") ||
2838                                            looking_at(buf, &i, "* shouts:") ||
2839                                            looking_at(buf, &i, "* c-shouts:") ||
2840                                            looking_at(buf, &i, "--> * ") ||
2841                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2842                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2843                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2844                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2845                 int p;
2846                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2847                 chattingPartner = -1;
2848
2849                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2850                 for(p=0; p<MAX_CHAT; p++) {
2851                     if(channel == atoi(chatPartner[p])) {
2852                     talker[0] = '['; strcat(talker, "] ");
2853                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2854                     chattingPartner = p; break;
2855                     }
2856                 } else
2857                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2858                 for(p=0; p<MAX_CHAT; p++) {
2859                     if(!strcmp("kibitzes", chatPartner[p])) {
2860                         talker[0] = '['; strcat(talker, "] ");
2861                         chattingPartner = p; break;
2862                     }
2863                 } else
2864                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2865                 for(p=0; p<MAX_CHAT; p++) {
2866                     if(!strcmp("whispers", chatPartner[p])) {
2867                         talker[0] = '['; strcat(talker, "] ");
2868                         chattingPartner = p; break;
2869                     }
2870                 } else
2871                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2872                   if(buf[i-8] == '-' && buf[i-3] == 't')
2873                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2874                     if(!strcmp("c-shouts", chatPartner[p])) {
2875                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2876                         chattingPartner = p; break;
2877                     }
2878                   }
2879                   if(chattingPartner < 0)
2880                   for(p=0; p<MAX_CHAT; p++) {
2881                     if(!strcmp("shouts", chatPartner[p])) {
2882                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2883                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2884                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2885                         chattingPartner = p; break;
2886                     }
2887                   }
2888                 }
2889                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2890                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2891                     talker[0] = 0; Colorize(ColorTell, FALSE);
2892                     chattingPartner = p; break;
2893                 }
2894                 if(chattingPartner<0) i = oldi; else {
2895                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2896                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2897                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2898                     started = STARTED_COMMENT;
2899                     parse_pos = 0; parse[0] = NULLCHAR;
2900                     savingComment = 3 + chattingPartner; // counts as TRUE
2901                     suppressKibitz = TRUE;
2902                     continue;
2903                 }
2904             } // [HGM] chat: end of patch
2905
2906             if (appData.zippyTalk || appData.zippyPlay) {
2907                 /* [DM] Backup address for color zippy lines */
2908                 backup = i;
2909 #if ZIPPY
2910                if (loggedOn == TRUE)
2911                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2912                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2913 #endif
2914             } // [DM] 'else { ' deleted
2915                 if (
2916                     /* Regular tells and says */
2917                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2918                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2919                     looking_at(buf, &i, "* says: ") ||
2920                     /* Don't color "message" or "messages" output */
2921                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2922                     looking_at(buf, &i, "*. * at *:*: ") ||
2923                     looking_at(buf, &i, "--* (*:*): ") ||
2924                     /* Message notifications (same color as tells) */
2925                     looking_at(buf, &i, "* has left a message ") ||
2926                     looking_at(buf, &i, "* just sent you a message:\n") ||
2927                     /* Whispers and kibitzes */
2928                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2929                     looking_at(buf, &i, "* kibitzes: ") ||
2930                     /* Channel tells */
2931                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2932
2933                   if (tkind == 1 && strchr(star_match[0], ':')) {
2934                       /* Avoid "tells you:" spoofs in channels */
2935                      tkind = 3;
2936                   }
2937                   if (star_match[0][0] == NULLCHAR ||
2938                       strchr(star_match[0], ' ') ||
2939                       (tkind == 3 && strchr(star_match[1], ' '))) {
2940                     /* Reject bogus matches */
2941                     i = oldi;
2942                   } else {
2943                     if (appData.colorize) {
2944                       if (oldi > next_out) {
2945                         SendToPlayer(&buf[next_out], oldi - next_out);
2946                         next_out = oldi;
2947                       }
2948                       switch (tkind) {
2949                       case 1:
2950                         Colorize(ColorTell, FALSE);
2951                         curColor = ColorTell;
2952                         break;
2953                       case 2:
2954                         Colorize(ColorKibitz, FALSE);
2955                         curColor = ColorKibitz;
2956                         break;
2957                       case 3:
2958                         p = strrchr(star_match[1], '(');
2959                         if (p == NULL) {
2960                           p = star_match[1];
2961                         } else {
2962                           p++;
2963                         }
2964                         if (atoi(p) == 1) {
2965                           Colorize(ColorChannel1, FALSE);
2966                           curColor = ColorChannel1;
2967                         } else {
2968                           Colorize(ColorChannel, FALSE);
2969                           curColor = ColorChannel;
2970                         }
2971                         break;
2972                       case 5:
2973                         curColor = ColorNormal;
2974                         break;
2975                       }
2976                     }
2977                     if (started == STARTED_NONE && appData.autoComment &&
2978                         (gameMode == IcsObserving ||
2979                          gameMode == IcsPlayingWhite ||
2980                          gameMode == IcsPlayingBlack)) {
2981                       parse_pos = i - oldi;
2982                       memcpy(parse, &buf[oldi], parse_pos);
2983                       parse[parse_pos] = NULLCHAR;
2984                       started = STARTED_COMMENT;
2985                       savingComment = TRUE;
2986                     } else {
2987                       started = STARTED_CHATTER;
2988                       savingComment = FALSE;
2989                     }
2990                     loggedOn = TRUE;
2991                     continue;
2992                   }
2993                 }
2994
2995                 if (looking_at(buf, &i, "* s-shouts: ") ||
2996                     looking_at(buf, &i, "* c-shouts: ")) {
2997                     if (appData.colorize) {
2998                         if (oldi > next_out) {
2999                             SendToPlayer(&buf[next_out], oldi - next_out);
3000                             next_out = oldi;
3001                         }
3002                         Colorize(ColorSShout, FALSE);
3003                         curColor = ColorSShout;
3004                     }
3005                     loggedOn = TRUE;
3006                     started = STARTED_CHATTER;
3007                     continue;
3008                 }
3009
3010                 if (looking_at(buf, &i, "--->")) {
3011                     loggedOn = TRUE;
3012                     continue;
3013                 }
3014
3015                 if (looking_at(buf, &i, "* shouts: ") ||
3016                     looking_at(buf, &i, "--> ")) {
3017                     if (appData.colorize) {
3018                         if (oldi > next_out) {
3019                             SendToPlayer(&buf[next_out], oldi - next_out);
3020                             next_out = oldi;
3021                         }
3022                         Colorize(ColorShout, FALSE);
3023                         curColor = ColorShout;
3024                     }
3025                     loggedOn = TRUE;
3026                     started = STARTED_CHATTER;
3027                     continue;
3028                 }
3029
3030                 if (looking_at( buf, &i, "Challenge:")) {
3031                     if (appData.colorize) {
3032                         if (oldi > next_out) {
3033                             SendToPlayer(&buf[next_out], oldi - next_out);
3034                             next_out = oldi;
3035                         }
3036                         Colorize(ColorChallenge, FALSE);
3037                         curColor = ColorChallenge;
3038                     }
3039                     loggedOn = TRUE;
3040                     continue;
3041                 }
3042
3043                 if (looking_at(buf, &i, "* offers you") ||
3044                     looking_at(buf, &i, "* offers to be") ||
3045                     looking_at(buf, &i, "* would like to") ||
3046                     looking_at(buf, &i, "* requests to") ||
3047                     looking_at(buf, &i, "Your opponent offers") ||
3048                     looking_at(buf, &i, "Your opponent requests")) {
3049
3050                     if (appData.colorize) {
3051                         if (oldi > next_out) {
3052                             SendToPlayer(&buf[next_out], oldi - next_out);
3053                             next_out = oldi;
3054                         }
3055                         Colorize(ColorRequest, FALSE);
3056                         curColor = ColorRequest;
3057                     }
3058                     continue;
3059                 }
3060
3061                 if (looking_at(buf, &i, "* (*) seeking")) {
3062                     if (appData.colorize) {
3063                         if (oldi > next_out) {
3064                             SendToPlayer(&buf[next_out], oldi - next_out);
3065                             next_out = oldi;
3066                         }
3067                         Colorize(ColorSeek, FALSE);
3068                         curColor = ColorSeek;
3069                     }
3070                     continue;
3071             }
3072
3073             if (looking_at(buf, &i, "\\   ")) {
3074                 if (prevColor != ColorNormal) {
3075                     if (oldi > next_out) {
3076                         SendToPlayer(&buf[next_out], oldi - next_out);
3077                         next_out = oldi;
3078                     }
3079                     Colorize(prevColor, TRUE);
3080                     curColor = prevColor;
3081                 }
3082                 if (savingComment) {
3083                     parse_pos = i - oldi;
3084                     memcpy(parse, &buf[oldi], parse_pos);
3085                     parse[parse_pos] = NULLCHAR;
3086                     started = STARTED_COMMENT;
3087                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3088                         chattingPartner = savingComment - 3; // kludge to remember the box
3089                 } else {
3090                     started = STARTED_CHATTER;
3091                 }
3092                 continue;
3093             }
3094
3095             if (looking_at(buf, &i, "Black Strength :") ||
3096                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3097                 looking_at(buf, &i, "<10>") ||
3098                 looking_at(buf, &i, "#@#")) {
3099                 /* Wrong board style */
3100                 loggedOn = TRUE;
3101                 SendToICS(ics_prefix);
3102                 SendToICS("set style 12\n");
3103                 SendToICS(ics_prefix);
3104                 SendToICS("refresh\n");
3105                 continue;
3106             }
3107
3108             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3109                 ICSInitScript();
3110                 have_sent_ICS_logon = 1;
3111                 continue;
3112             }
3113
3114             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3115                 (looking_at(buf, &i, "\n<12> ") ||
3116                  looking_at(buf, &i, "<12> "))) {
3117                 loggedOn = TRUE;
3118                 if (oldi > next_out) {
3119                     SendToPlayer(&buf[next_out], oldi - next_out);
3120                 }
3121                 next_out = i;
3122                 started = STARTED_BOARD;
3123                 parse_pos = 0;
3124                 continue;
3125             }
3126
3127             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3128                 looking_at(buf, &i, "<b1> ")) {
3129                 if (oldi > next_out) {
3130                     SendToPlayer(&buf[next_out], oldi - next_out);
3131                 }
3132                 next_out = i;
3133                 started = STARTED_HOLDINGS;
3134                 parse_pos = 0;
3135                 continue;
3136             }
3137
3138             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3139                 loggedOn = TRUE;
3140                 /* Header for a move list -- first line */
3141
3142                 switch (ics_getting_history) {
3143                   case H_FALSE:
3144                     switch (gameMode) {
3145                       case IcsIdle:
3146                       case BeginningOfGame:
3147                         /* User typed "moves" or "oldmoves" while we
3148                            were idle.  Pretend we asked for these
3149                            moves and soak them up so user can step
3150                            through them and/or save them.
3151                            */
3152                         Reset(FALSE, TRUE);
3153                         gameMode = IcsObserving;
3154                         ModeHighlight();
3155                         ics_gamenum = -1;
3156                         ics_getting_history = H_GOT_UNREQ_HEADER;
3157                         break;
3158                       case EditGame: /*?*/
3159                       case EditPosition: /*?*/
3160                         /* Should above feature work in these modes too? */
3161                         /* For now it doesn't */
3162                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3163                         break;
3164                       default:
3165                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3166                         break;
3167                     }
3168                     break;
3169                   case H_REQUESTED:
3170                     /* Is this the right one? */
3171                     if (gameInfo.white && gameInfo.black &&
3172                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3173                         strcmp(gameInfo.black, star_match[2]) == 0) {
3174                         /* All is well */
3175                         ics_getting_history = H_GOT_REQ_HEADER;
3176                     }
3177                     break;
3178                   case H_GOT_REQ_HEADER:
3179                   case H_GOT_UNREQ_HEADER:
3180                   case H_GOT_UNWANTED_HEADER:
3181                   case H_GETTING_MOVES:
3182                     /* Should not happen */
3183                     DisplayError(_("Error gathering move list: two headers"), 0);
3184                     ics_getting_history = H_FALSE;
3185                     break;
3186                 }
3187
3188                 /* Save player ratings into gameInfo if needed */
3189                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3190                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3191                     (gameInfo.whiteRating == -1 ||
3192                      gameInfo.blackRating == -1)) {
3193
3194                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3195                     gameInfo.blackRating = string_to_rating(star_match[3]);
3196                     if (appData.debugMode)
3197                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3198                               gameInfo.whiteRating, gameInfo.blackRating);
3199                 }
3200                 continue;
3201             }
3202
3203             if (looking_at(buf, &i,
3204               "* * match, initial time: * minute*, increment: * second")) {
3205                 /* Header for a move list -- second line */
3206                 /* Initial board will follow if this is a wild game */
3207                 if (gameInfo.event != NULL) free(gameInfo.event);
3208                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3209                 gameInfo.event = StrSave(str);
3210                 /* [HGM] we switched variant. Translate boards if needed. */
3211                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3212                 continue;
3213             }
3214
3215             if (looking_at(buf, &i, "Move  ")) {
3216                 /* Beginning of a move list */
3217                 switch (ics_getting_history) {
3218                   case H_FALSE:
3219                     /* Normally should not happen */
3220                     /* Maybe user hit reset while we were parsing */
3221                     break;
3222                   case H_REQUESTED:
3223                     /* Happens if we are ignoring a move list that is not
3224                      * the one we just requested.  Common if the user
3225                      * tries to observe two games without turning off
3226                      * getMoveList */
3227                     break;
3228                   case H_GETTING_MOVES:
3229                     /* Should not happen */
3230                     DisplayError(_("Error gathering move list: nested"), 0);
3231                     ics_getting_history = H_FALSE;
3232                     break;
3233                   case H_GOT_REQ_HEADER:
3234                     ics_getting_history = H_GETTING_MOVES;
3235                     started = STARTED_MOVES;
3236                     parse_pos = 0;
3237                     if (oldi > next_out) {
3238                         SendToPlayer(&buf[next_out], oldi - next_out);
3239                     }
3240                     break;
3241                   case H_GOT_UNREQ_HEADER:
3242                     ics_getting_history = H_GETTING_MOVES;
3243                     started = STARTED_MOVES_NOHIDE;
3244                     parse_pos = 0;
3245                     break;
3246                   case H_GOT_UNWANTED_HEADER:
3247                     ics_getting_history = H_FALSE;
3248                     break;
3249                 }
3250                 continue;
3251             }
3252
3253             if (looking_at(buf, &i, "% ") ||
3254                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3255                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3256                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3257                     soughtPending = FALSE;
3258                     seekGraphUp = TRUE;
3259                     DrawSeekGraph();
3260                 }
3261                 if(suppressKibitz) next_out = i;
3262                 savingComment = FALSE;
3263                 suppressKibitz = 0;
3264                 switch (started) {
3265                   case STARTED_MOVES:
3266                   case STARTED_MOVES_NOHIDE:
3267                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3268                     parse[parse_pos + i - oldi] = NULLCHAR;
3269                     ParseGameHistory(parse);
3270 #if ZIPPY
3271                     if (appData.zippyPlay && first.initDone) {
3272                         FeedMovesToProgram(&first, forwardMostMove);
3273                         if (gameMode == IcsPlayingWhite) {
3274                             if (WhiteOnMove(forwardMostMove)) {
3275                                 if (first.sendTime) {
3276                                   if (first.useColors) {
3277                                     SendToProgram("black\n", &first);
3278                                   }
3279                                   SendTimeRemaining(&first, TRUE);
3280                                 }
3281                                 if (first.useColors) {
3282                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3283                                 }
3284                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3285                                 first.maybeThinking = TRUE;
3286                             } else {
3287                                 if (first.usePlayother) {
3288                                   if (first.sendTime) {
3289                                     SendTimeRemaining(&first, TRUE);
3290                                   }
3291                                   SendToProgram("playother\n", &first);
3292                                   firstMove = FALSE;
3293                                 } else {
3294                                   firstMove = TRUE;
3295                                 }
3296                             }
3297                         } else if (gameMode == IcsPlayingBlack) {
3298                             if (!WhiteOnMove(forwardMostMove)) {
3299                                 if (first.sendTime) {
3300                                   if (first.useColors) {
3301                                     SendToProgram("white\n", &first);
3302                                   }
3303                                   SendTimeRemaining(&first, FALSE);
3304                                 }
3305                                 if (first.useColors) {
3306                                   SendToProgram("black\n", &first);
3307                                 }
3308                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3309                                 first.maybeThinking = TRUE;
3310                             } else {
3311                                 if (first.usePlayother) {
3312                                   if (first.sendTime) {
3313                                     SendTimeRemaining(&first, FALSE);
3314                                   }
3315                                   SendToProgram("playother\n", &first);
3316                                   firstMove = FALSE;
3317                                 } else {
3318                                   firstMove = TRUE;
3319                                 }
3320                             }
3321                         }
3322                     }
3323 #endif
3324                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3325                         /* Moves came from oldmoves or moves command
3326                            while we weren't doing anything else.
3327                            */
3328                         currentMove = forwardMostMove;
3329                         ClearHighlights();/*!!could figure this out*/
3330                         flipView = appData.flipView;
3331                         DrawPosition(TRUE, boards[currentMove]);
3332                         DisplayBothClocks();
3333                         snprintf(str, MSG_SIZ, "%s vs. %s",
3334                                 gameInfo.white, gameInfo.black);
3335                         DisplayTitle(str);
3336                         gameMode = IcsIdle;
3337                     } else {
3338                         /* Moves were history of an active game */
3339                         if (gameInfo.resultDetails != NULL) {
3340                             free(gameInfo.resultDetails);
3341                             gameInfo.resultDetails = NULL;
3342                         }
3343                     }
3344                     HistorySet(parseList, backwardMostMove,
3345                                forwardMostMove, currentMove-1);
3346                     DisplayMove(currentMove - 1);
3347                     if (started == STARTED_MOVES) next_out = i;
3348                     started = STARTED_NONE;
3349                     ics_getting_history = H_FALSE;
3350                     break;
3351
3352                   case STARTED_OBSERVE:
3353         &