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