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