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