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