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