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