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