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