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