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