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