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