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