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