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