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