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