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