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