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