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