Added a command-line option -keepLineBreaksICS true/false to control line joining.
[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     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1553       if (StrCaseStr(e, variantNames[i])) {
1554         v = (VariantClass) i;
1555         found = TRUE;
1556         break;
1557       }
1558     }
1559
1560     if (!found) {
1561       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1562           || StrCaseStr(e, "wild/fr") 
1563           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1564         v = VariantFischeRandom;
1565       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1566                  (i = 1, p = StrCaseStr(e, "w"))) {
1567         p += i;
1568         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1569         if (isdigit(*p)) {
1570           wnum = atoi(p);
1571         } else {
1572           wnum = -1;
1573         }
1574         switch (wnum) {
1575         case 0: /* FICS only, actually */
1576         case 1:
1577           /* Castling legal even if K starts on d-file */
1578           v = VariantWildCastle;
1579           break;
1580         case 2:
1581         case 3:
1582         case 4:
1583           /* Castling illegal even if K & R happen to start in
1584              normal positions. */
1585           v = VariantNoCastle;
1586           break;
1587         case 5:
1588         case 7:
1589         case 8:
1590         case 10:
1591         case 11:
1592         case 12:
1593         case 13:
1594         case 14:
1595         case 15:
1596         case 18:
1597         case 19:
1598           /* Castling legal iff K & R start in normal positions */
1599           v = VariantNormal;
1600           break;
1601         case 6:
1602         case 20:
1603         case 21:
1604           /* Special wilds for position setup; unclear what to do here */
1605           v = VariantLoadable;
1606           break;
1607         case 9:
1608           /* Bizarre ICC game */
1609           v = VariantTwoKings;
1610           break;
1611         case 16:
1612           v = VariantKriegspiel;
1613           break;
1614         case 17:
1615           v = VariantLosers;
1616           break;
1617         case 22:
1618           v = VariantFischeRandom;
1619           break;
1620         case 23:
1621           v = VariantCrazyhouse;
1622           break;
1623         case 24:
1624           v = VariantBughouse;
1625           break;
1626         case 25:
1627           v = Variant3Check;
1628           break;
1629         case 26:
1630           /* Not quite the same as FICS suicide! */
1631           v = VariantGiveaway;
1632           break;
1633         case 27:
1634           v = VariantAtomic;
1635           break;
1636         case 28:
1637           v = VariantShatranj;
1638           break;
1639
1640         /* Temporary names for future ICC types.  The name *will* change in 
1641            the next xboard/WinBoard release after ICC defines it. */
1642         case 29:
1643           v = Variant29;
1644           break;
1645         case 30:
1646           v = Variant30;
1647           break;
1648         case 31:
1649           v = Variant31;
1650           break;
1651         case 32:
1652           v = Variant32;
1653           break;
1654         case 33:
1655           v = Variant33;
1656           break;
1657         case 34:
1658           v = Variant34;
1659           break;
1660         case 35:
1661           v = Variant35;
1662           break;
1663         case 36:
1664           v = Variant36;
1665           break;
1666         case 37:
1667           v = VariantShogi;
1668           break;
1669         case 38:
1670           v = VariantXiangqi;
1671           break;
1672         case 39:
1673           v = VariantCourier;
1674           break;
1675         case 40:
1676           v = VariantGothic;
1677           break;
1678         case 41:
1679           v = VariantCapablanca;
1680           break;
1681         case 42:
1682           v = VariantKnightmate;
1683           break;
1684         case 43:
1685           v = VariantFairy;
1686           break;
1687         case 44:
1688           v = VariantCylinder;
1689           break;
1690         case 45:
1691           v = VariantFalcon;
1692           break;
1693         case 46:
1694           v = VariantCapaRandom;
1695           break;
1696         case 47:
1697           v = VariantBerolina;
1698           break;
1699         case 48:
1700           v = VariantJanus;
1701           break;
1702         case 49:
1703           v = VariantSuper;
1704           break;
1705         case 50:
1706           v = VariantGreat;
1707           break;
1708         case -1:
1709           /* Found "wild" or "w" in the string but no number;
1710              must assume it's normal chess. */
1711           v = VariantNormal;
1712           break;
1713         default:
1714           sprintf(buf, _("Unknown wild type %d"), wnum);
1715           DisplayError(buf, 0);
1716           v = VariantUnknown;
1717           break;
1718         }
1719       }
1720     }
1721     if (appData.debugMode) {
1722       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1723               e, wnum, VariantName(v));
1724     }
1725     return v;
1726 }
1727
1728 static int leftover_start = 0, leftover_len = 0;
1729 char star_match[STAR_MATCH_N][MSG_SIZ];
1730
1731 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1732    advance *index beyond it, and set leftover_start to the new value of
1733    *index; else return FALSE.  If pattern contains the character '*', it
1734    matches any sequence of characters not containing '\r', '\n', or the
1735    character following the '*' (if any), and the matched sequence(s) are
1736    copied into star_match.
1737    */
1738 int
1739 looking_at(buf, index, pattern)
1740      char *buf;
1741      int *index;
1742      char *pattern;
1743 {
1744     char *bufp = &buf[*index], *patternp = pattern;
1745     int star_count = 0;
1746     char *matchp = star_match[0];
1747     
1748     for (;;) {
1749         if (*patternp == NULLCHAR) {
1750             *index = leftover_start = bufp - buf;
1751             *matchp = NULLCHAR;
1752             return TRUE;
1753         }
1754         if (*bufp == NULLCHAR) return FALSE;
1755         if (*patternp == '*') {
1756             if (*bufp == *(patternp + 1)) {
1757                 *matchp = NULLCHAR;
1758                 matchp = star_match[++star_count];
1759                 patternp += 2;
1760                 bufp++;
1761                 continue;
1762             } else if (*bufp == '\n' || *bufp == '\r') {
1763                 patternp++;
1764                 if (*patternp == NULLCHAR)
1765                   continue;
1766                 else
1767                   return FALSE;
1768             } else {
1769                 *matchp++ = *bufp++;
1770                 continue;
1771             }
1772         }
1773         if (*patternp != *bufp) return FALSE;
1774         patternp++;
1775         bufp++;
1776     }
1777 }
1778
1779 void
1780 SendToPlayer(data, length)
1781      char *data;
1782      int length;
1783 {
1784     int error, outCount;
1785     outCount = OutputToProcess(NoProc, data, length, &error);
1786     if (outCount < length) {
1787         DisplayFatalError(_("Error writing to display"), error, 1);
1788     }
1789 }
1790
1791 void
1792 PackHolding(packed, holding)
1793      char packed[];
1794      char *holding;
1795 {
1796     char *p = holding;
1797     char *q = packed;
1798     int runlength = 0;
1799     int curr = 9999;
1800     do {
1801         if (*p == curr) {
1802             runlength++;
1803         } else {
1804             switch (runlength) {
1805               case 0:
1806                 break;
1807               case 1:
1808                 *q++ = curr;
1809                 break;
1810               case 2:
1811                 *q++ = curr;
1812                 *q++ = curr;
1813                 break;
1814               default:
1815                 sprintf(q, "%d", runlength);
1816                 while (*q) q++;
1817                 *q++ = curr;
1818                 break;
1819             }
1820             runlength = 1;
1821             curr = *p;
1822         }
1823     } while (*p++);
1824     *q = NULLCHAR;
1825 }
1826
1827 /* Telnet protocol requests from the front end */
1828 void
1829 TelnetRequest(ddww, option)
1830      unsigned char ddww, option;
1831 {
1832     unsigned char msg[3];
1833     int outCount, outError;
1834
1835     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1836
1837     if (appData.debugMode) {
1838         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1839         switch (ddww) {
1840           case TN_DO:
1841             ddwwStr = "DO";
1842             break;
1843           case TN_DONT:
1844             ddwwStr = "DONT";
1845             break;
1846           case TN_WILL:
1847             ddwwStr = "WILL";
1848             break;
1849           case TN_WONT:
1850             ddwwStr = "WONT";
1851             break;
1852           default:
1853             ddwwStr = buf1;
1854             sprintf(buf1, "%d", ddww);
1855             break;
1856         }
1857         switch (option) {
1858           case TN_ECHO:
1859             optionStr = "ECHO";
1860             break;
1861           default:
1862             optionStr = buf2;
1863             sprintf(buf2, "%d", option);
1864             break;
1865         }
1866         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1867     }
1868     msg[0] = TN_IAC;
1869     msg[1] = ddww;
1870     msg[2] = option;
1871     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1872     if (outCount < 3) {
1873         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1874     }
1875 }
1876
1877 void
1878 DoEcho()
1879 {
1880     if (!appData.icsActive) return;
1881     TelnetRequest(TN_DO, TN_ECHO);
1882 }
1883
1884 void
1885 DontEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DONT, TN_ECHO);
1889 }
1890
1891 void
1892 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1893 {
1894     /* put the holdings sent to us by the server on the board holdings area */
1895     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1896     char p;
1897     ChessSquare piece;
1898
1899     if(gameInfo.holdingsWidth < 2)  return;
1900
1901     if( (int)lowestPiece >= BlackPawn ) {
1902         holdingsColumn = 0;
1903         countsColumn = 1;
1904         holdingsStartRow = BOARD_HEIGHT-1;
1905         direction = -1;
1906     } else {
1907         holdingsColumn = BOARD_WIDTH-1;
1908         countsColumn = BOARD_WIDTH-2;
1909         holdingsStartRow = 0;
1910         direction = 1;
1911     }
1912
1913     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1914         board[i][holdingsColumn] = EmptySquare;
1915         board[i][countsColumn]   = (ChessSquare) 0;
1916     }
1917     while( (p=*holdings++) != NULLCHAR ) {
1918         piece = CharToPiece( ToUpper(p) );
1919         if(piece == EmptySquare) continue;
1920         /*j = (int) piece - (int) WhitePawn;*/
1921         j = PieceToNumber(piece);
1922         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1923         if(j < 0) continue;               /* should not happen */
1924         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1925         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1926         board[holdingsStartRow+j*direction][countsColumn]++;
1927     }
1928
1929 }
1930
1931
1932 void
1933 VariantSwitch(Board board, VariantClass newVariant)
1934 {
1935    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1936    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1937 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1938
1939    startedFromPositionFile = FALSE;
1940    if(gameInfo.variant == newVariant) return;
1941
1942    /* [HGM] This routine is called each time an assignment is made to
1943     * gameInfo.variant during a game, to make sure the board sizes
1944     * are set to match the new variant. If that means adding or deleting
1945     * holdings, we shift the playing board accordingly
1946     * This kludge is needed because in ICS observe mode, we get boards
1947     * of an ongoing game without knowing the variant, and learn about the
1948     * latter only later. This can be because of the move list we requested,
1949     * in which case the game history is refilled from the beginning anyway,
1950     * but also when receiving holdings of a crazyhouse game. In the latter
1951     * case we want to add those holdings to the already received position.
1952     */
1953
1954
1955   if (appData.debugMode) {
1956     fprintf(debugFP, "Switch board from %s to %s\n",
1957                VariantName(gameInfo.variant), VariantName(newVariant));
1958     setbuf(debugFP, NULL);
1959   }
1960     shuffleOpenings = 0;       /* [HGM] shuffle */
1961     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1962     switch(newVariant) {
1963             case VariantShogi:
1964               newWidth = 9;  newHeight = 9;
1965               gameInfo.holdingsSize = 7;
1966             case VariantBughouse:
1967             case VariantCrazyhouse:
1968               newHoldingsWidth = 2; break;
1969             default:
1970               newHoldingsWidth = gameInfo.holdingsSize = 0;
1971     }
1972
1973     if(newWidth  != gameInfo.boardWidth  ||
1974        newHeight != gameInfo.boardHeight ||
1975        newHoldingsWidth != gameInfo.holdingsWidth ) {
1976
1977         /* shift position to new playing area, if needed */
1978         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1979            for(i=0; i<BOARD_HEIGHT; i++) 
1980                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1981                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1982                                                      board[i][j];
1983            for(i=0; i<newHeight; i++) {
1984                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1985                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1986            }
1987         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1988            for(i=0; i<BOARD_HEIGHT; i++)
1989                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1990                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1991                                                  board[i][j];
1992         }
1993
1994         gameInfo.boardWidth  = newWidth;
1995         gameInfo.boardHeight = newHeight;
1996         gameInfo.holdingsWidth = newHoldingsWidth;
1997         gameInfo.variant = newVariant;
1998         InitDrawingSizes(-2, 0);
1999
2000         /* [HGM] The following should definitely be solved in a better way */
2001         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2002     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2003
2004     forwardMostMove = oldForwardMostMove;
2005     backwardMostMove = oldBackwardMostMove;
2006     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2007 }
2008
2009 static int loggedOn = FALSE;
2010
2011 /*-- Game start info cache: --*/
2012 int gs_gamenum;
2013 char gs_kind[MSG_SIZ];
2014 static char player1Name[128] = "";
2015 static char player2Name[128] = "";
2016 static int player1Rating = -1;
2017 static int player2Rating = -1;
2018 /*----------------------------*/
2019
2020 ColorClass curColor = ColorNormal;
2021 int suppressKibitz = 0;
2022
2023 void
2024 read_from_ics(isr, closure, data, count, error)
2025      InputSourceRef isr;
2026      VOIDSTAR closure;
2027      char *data;
2028      int count;
2029      int error;
2030 {
2031 #define BUF_SIZE 8192
2032 #define STARTED_NONE 0
2033 #define STARTED_MOVES 1
2034 #define STARTED_BOARD 2
2035 #define STARTED_OBSERVE 3
2036 #define STARTED_HOLDINGS 4
2037 #define STARTED_CHATTER 5
2038 #define STARTED_COMMENT 6
2039 #define STARTED_MOVES_NOHIDE 7
2040     
2041     static int started = STARTED_NONE;
2042     static char parse[20000];
2043     static int parse_pos = 0;
2044     static char buf[BUF_SIZE + 1];
2045     static int firstTime = TRUE, intfSet = FALSE;
2046     static ColorClass prevColor = ColorNormal;
2047     static int savingComment = FALSE;
2048     char str[500];
2049     int i, oldi;
2050     int buf_len;
2051     int next_out;
2052     int tkind;
2053     int backup;    /* [DM] For zippy color lines */
2054     char *p;
2055     char talker[MSG_SIZ]; // [HGM] chat
2056     int channel;
2057
2058     if (appData.debugMode) {
2059       if (!error) {
2060         fprintf(debugFP, "<ICS: ");
2061         show_bytes(debugFP, data, count);
2062         fprintf(debugFP, "\n");
2063       }
2064     }
2065
2066     if (appData.debugMode) { int f = forwardMostMove;
2067         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2068                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2069     }
2070     if (count > 0) {
2071         /* If last read ended with a partial line that we couldn't parse,
2072            prepend it to the new read and try again. */
2073         if (leftover_len > 0) {
2074             for (i=0; i<leftover_len; i++)
2075               buf[i] = buf[leftover_start + i];
2076         }
2077
2078         /* Copy in new characters, removing nulls and \r's */
2079         buf_len = leftover_len;
2080         for (i = 0; i < count; i++) {
2081             if (data[i] != NULLCHAR && data[i] != '\r')
2082               buf[buf_len++] = data[i];
2083             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2084                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2085                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2086                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2087                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2088             }
2089         }
2090
2091         buf[buf_len] = NULLCHAR;
2092         next_out = leftover_len;
2093         leftover_start = 0;
2094         
2095         i = 0;
2096         while (i < buf_len) {
2097             /* Deal with part of the TELNET option negotiation
2098                protocol.  We refuse to do anything beyond the
2099                defaults, except that we allow the WILL ECHO option,
2100                which ICS uses to turn off password echoing when we are
2101                directly connected to it.  We reject this option
2102                if localLineEditing mode is on (always on in xboard)
2103                and we are talking to port 23, which might be a real
2104                telnet server that will try to keep WILL ECHO on permanently.
2105              */
2106             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2107                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2108                 unsigned char option;
2109                 oldi = i;
2110                 switch ((unsigned char) buf[++i]) {
2111                   case TN_WILL:
2112                     if (appData.debugMode)
2113                       fprintf(debugFP, "\n<WILL ");
2114                     switch (option = (unsigned char) buf[++i]) {
2115                       case TN_ECHO:
2116                         if (appData.debugMode)
2117                           fprintf(debugFP, "ECHO ");
2118                         /* Reply only if this is a change, according
2119                            to the protocol rules. */
2120                         if (remoteEchoOption) break;
2121                         if (appData.localLineEditing &&
2122                             atoi(appData.icsPort) == TN_PORT) {
2123                             TelnetRequest(TN_DONT, TN_ECHO);
2124                         } else {
2125                             EchoOff();
2126                             TelnetRequest(TN_DO, TN_ECHO);
2127                             remoteEchoOption = TRUE;
2128                         }
2129                         break;
2130                       default:
2131                         if (appData.debugMode)
2132                           fprintf(debugFP, "%d ", option);
2133                         /* Whatever this is, we don't want it. */
2134                         TelnetRequest(TN_DONT, option);
2135                         break;
2136                     }
2137                     break;
2138                   case TN_WONT:
2139                     if (appData.debugMode)
2140                       fprintf(debugFP, "\n<WONT ");
2141                     switch (option = (unsigned char) buf[++i]) {
2142                       case TN_ECHO:
2143                         if (appData.debugMode)
2144                           fprintf(debugFP, "ECHO ");
2145                         /* Reply only if this is a change, according
2146                            to the protocol rules. */
2147                         if (!remoteEchoOption) break;
2148                         EchoOn();
2149                         TelnetRequest(TN_DONT, TN_ECHO);
2150                         remoteEchoOption = FALSE;
2151                         break;
2152                       default:
2153                         if (appData.debugMode)
2154                           fprintf(debugFP, "%d ", (unsigned char) option);
2155                         /* Whatever this is, it must already be turned
2156                            off, because we never agree to turn on
2157                            anything non-default, so according to the
2158                            protocol rules, we don't reply. */
2159                         break;
2160                     }
2161                     break;
2162                   case TN_DO:
2163                     if (appData.debugMode)
2164                       fprintf(debugFP, "\n<DO ");
2165                     switch (option = (unsigned char) buf[++i]) {
2166                       default:
2167                         /* Whatever this is, we refuse to do it. */
2168                         if (appData.debugMode)
2169                           fprintf(debugFP, "%d ", option);
2170                         TelnetRequest(TN_WONT, option);
2171                         break;
2172                     }
2173                     break;
2174                   case TN_DONT:
2175                     if (appData.debugMode)
2176                       fprintf(debugFP, "\n<DONT ");
2177                     switch (option = (unsigned char) buf[++i]) {
2178                       default:
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "%d ", option);
2181                         /* Whatever this is, we are already not doing
2182                            it, because we never agree to do anything
2183                            non-default, so according to the protocol
2184                            rules, we don't reply. */
2185                         break;
2186                     }
2187                     break;
2188                   case TN_IAC:
2189                     if (appData.debugMode)
2190                       fprintf(debugFP, "\n<IAC ");
2191                     /* Doubled IAC; pass it through */
2192                     i--;
2193                     break;
2194                   default:
2195                     if (appData.debugMode)
2196                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2197                     /* Drop all other telnet commands on the floor */
2198                     break;
2199                 }
2200                 if (oldi > next_out)
2201                   SendToPlayer(&buf[next_out], oldi - next_out);
2202                 if (++i > next_out)
2203                   next_out = i;
2204                 continue;
2205             }
2206                 
2207             /* OK, this at least will *usually* work */
2208             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2209                 loggedOn = TRUE;
2210             }
2211             
2212             if (loggedOn && !intfSet) {
2213                 if (ics_type == ICS_ICC) {
2214                   sprintf(str,
2215                           "/set-quietly interface %s\n/set-quietly style 12\n",
2216                           programVersion);
2217                   strcat(str, "/set-quietly wrap 0\n");
2218
2219                 } else if (ics_type == ICS_CHESSNET) {
2220                   sprintf(str, "/style 12\n");
2221                 } else {
2222                   strcpy(str, "alias $ @\n$set interface ");
2223                   strcat(str, programVersion);
2224                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2225 #ifdef WIN32
2226                   strcat(str, "$iset nohighlight 1\n");
2227 #endif
2228                   strcat(str, "$iset nowrap 1\n");
2229                   strcat(str, "$iset lock 1\n$style 12\n");
2230                 }
2231                 SendToICS(str);
2232                 NotifyFrontendLogin();
2233                 intfSet = TRUE;
2234             }
2235
2236             if (started == STARTED_COMMENT) {
2237                 /* Accumulate characters in comment */
2238                 parse[parse_pos++] = buf[i];
2239                 if (buf[i] == '\n') {
2240                     parse[parse_pos] = NULLCHAR;
2241                     if(chattingPartner>=0) {
2242                         char mess[MSG_SIZ];
2243                         sprintf(mess, "%s%s", talker, parse);
2244                         OutputChatMessage(chattingPartner, mess);
2245                         chattingPartner = -1;
2246                     } else
2247                     if(!suppressKibitz) // [HGM] kibitz
2248                         AppendComment(forwardMostMove, StripHighlight(parse));
2249                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2250                         int nrDigit = 0, nrAlph = 0, i;
2251                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2252                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2253                         parse[parse_pos] = NULLCHAR;
2254                         // try to be smart: if it does not look like search info, it should go to
2255                         // ICS interaction window after all, not to engine-output window.
2256                         for(i=0; i<parse_pos; i++) { // count letters and digits
2257                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2258                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2259                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2260                         }
2261                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2262                             int depth=0; float score;
2263                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2264                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2265                                 pvInfoList[forwardMostMove-1].depth = depth;
2266                                 pvInfoList[forwardMostMove-1].score = 100*score;
2267                             }
2268                             OutputKibitz(suppressKibitz, parse);
2269                         } else {
2270                             char tmp[MSG_SIZ];
2271                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2272                             SendToPlayer(tmp, strlen(tmp));
2273                         }
2274                     }
2275                     started = STARTED_NONE;
2276                 } else {
2277                     /* Don't match patterns against characters in chatter */
2278                     i++;
2279                     continue;
2280                 }
2281             }
2282             if (started == STARTED_CHATTER) {
2283                 if (buf[i] != '\n') {
2284                     /* Don't match patterns against characters in chatter */
2285                     i++;
2286                     continue;
2287                 }
2288                 started = STARTED_NONE;
2289             }
2290
2291             /* Kludge to deal with rcmd protocol */
2292             if (firstTime && looking_at(buf, &i, "\001*")) {
2293                 DisplayFatalError(&buf[1], 0, 1);
2294                 continue;
2295             } else {
2296                 firstTime = FALSE;
2297             }
2298
2299             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2300                 ics_type = ICS_ICC;
2301                 ics_prefix = "/";
2302                 if (appData.debugMode)
2303                   fprintf(debugFP, "ics_type %d\n", ics_type);
2304                 continue;
2305             }
2306             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2307                 ics_type = ICS_FICS;
2308                 ics_prefix = "$";
2309                 if (appData.debugMode)
2310                   fprintf(debugFP, "ics_type %d\n", ics_type);
2311                 continue;
2312             }
2313             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2314                 ics_type = ICS_CHESSNET;
2315                 ics_prefix = "/";
2316                 if (appData.debugMode)
2317                   fprintf(debugFP, "ics_type %d\n", ics_type);
2318                 continue;
2319             }
2320
2321             if (!loggedOn &&
2322                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2323                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2324                  looking_at(buf, &i, "will be \"*\""))) {
2325               strcpy(ics_handle, star_match[0]);
2326               continue;
2327             }
2328
2329             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2330               char buf[MSG_SIZ];
2331               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2332               DisplayIcsInteractionTitle(buf);
2333               have_set_title = TRUE;
2334             }
2335
2336             /* skip finger notes */
2337             if (started == STARTED_NONE &&
2338                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2339                  (buf[i] == '1' && buf[i+1] == '0')) &&
2340                 buf[i+2] == ':' && buf[i+3] == ' ') {
2341               started = STARTED_CHATTER;
2342               i += 3;
2343               continue;
2344             }
2345
2346             /* skip formula vars */
2347             if (started == STARTED_NONE &&
2348                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2349               started = STARTED_CHATTER;
2350               i += 3;
2351               continue;
2352             }
2353
2354             oldi = i;
2355             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2356             if (appData.autoKibitz && started == STARTED_NONE && 
2357                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2358                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2359                 if(looking_at(buf, &i, "* kibitzes: ") &&
2360                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2361                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2362                         suppressKibitz = TRUE;
2363                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2364                                 && (gameMode == IcsPlayingWhite)) ||
2365                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2366                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2367                             started = STARTED_CHATTER; // own kibitz we simply discard
2368                         else {
2369                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2370                             parse_pos = 0; parse[0] = NULLCHAR;
2371                             savingComment = TRUE;
2372                             suppressKibitz = gameMode != IcsObserving ? 2 :
2373                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2374                         } 
2375                         continue;
2376                 } else
2377                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2378                     started = STARTED_CHATTER;
2379                     suppressKibitz = TRUE;
2380                 }
2381             } // [HGM] kibitz: end of patch
2382
2383 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2384
2385             // [HGM] chat: intercept tells by users for which we have an open chat window
2386             channel = -1;
2387             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2388                                            looking_at(buf, &i, "* whispers:") ||
2389                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2390                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2391                 int p;
2392                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2393                 chattingPartner = -1;
2394
2395                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2396                 for(p=0; p<MAX_CHAT; p++) {
2397                     if(channel == atoi(chatPartner[p])) {
2398                     talker[0] = '['; strcat(talker, "]");
2399                     chattingPartner = p; break;
2400                     }
2401                 } else
2402                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2403                 for(p=0; p<MAX_CHAT; p++) {
2404                     if(!strcmp("WHISPER", chatPartner[p])) {
2405                         talker[0] = '['; strcat(talker, "]");
2406                         chattingPartner = p; break;
2407                     }
2408                 }
2409                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2410                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2411                     talker[0] = 0;
2412                     chattingPartner = p; break;
2413                 }
2414                 if(chattingPartner<0) i = oldi; else {
2415                     started = STARTED_COMMENT;
2416                     parse_pos = 0; parse[0] = NULLCHAR;
2417                     savingComment = TRUE;
2418                     suppressKibitz = TRUE;
2419                 }
2420             } // [HGM] chat: end of patch
2421
2422             if (appData.zippyTalk || appData.zippyPlay) {
2423                 /* [DM] Backup address for color zippy lines */
2424                 backup = i;
2425 #if ZIPPY
2426        #ifdef WIN32
2427                if (loggedOn == TRUE)
2428                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2429                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2430        #else
2431                 if (ZippyControl(buf, &i) ||
2432                     ZippyConverse(buf, &i) ||
2433                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2434                       loggedOn = TRUE;
2435                       if (!appData.colorize) continue;
2436                 }
2437        #endif
2438 #endif
2439             } // [DM] 'else { ' deleted
2440                 if (
2441                     /* Regular tells and says */
2442                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2443                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2444                     looking_at(buf, &i, "* says: ") ||
2445                     /* Don't color "message" or "messages" output */
2446                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2447                     looking_at(buf, &i, "*. * at *:*: ") ||
2448                     looking_at(buf, &i, "--* (*:*): ") ||
2449                     /* Message notifications (same color as tells) */
2450                     looking_at(buf, &i, "* has left a message ") ||
2451                     looking_at(buf, &i, "* just sent you a message:\n") ||
2452                     /* Whispers and kibitzes */
2453                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2454                     looking_at(buf, &i, "* kibitzes: ") ||
2455                     /* Channel tells */
2456                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2457
2458                   if (tkind == 1 && strchr(star_match[0], ':')) {
2459                       /* Avoid "tells you:" spoofs in channels */
2460                      tkind = 3;
2461                   }
2462                   if (star_match[0][0] == NULLCHAR ||
2463                       strchr(star_match[0], ' ') ||
2464                       (tkind == 3 && strchr(star_match[1], ' '))) {
2465                     /* Reject bogus matches */
2466                     i = oldi;
2467                   } else {
2468                     if (appData.colorize) {
2469                       if (oldi > next_out) {
2470                         SendToPlayer(&buf[next_out], oldi - next_out);
2471                         next_out = oldi;
2472                       }
2473                       switch (tkind) {
2474                       case 1:
2475                         Colorize(ColorTell, FALSE);
2476                         curColor = ColorTell;
2477                         break;
2478                       case 2:
2479                         Colorize(ColorKibitz, FALSE);
2480                         curColor = ColorKibitz;
2481                         break;
2482                       case 3:
2483                         p = strrchr(star_match[1], '(');
2484                         if (p == NULL) {
2485                           p = star_match[1];
2486                         } else {
2487                           p++;
2488                         }
2489                         if (atoi(p) == 1) {
2490                           Colorize(ColorChannel1, FALSE);
2491                           curColor = ColorChannel1;
2492                         } else {
2493                           Colorize(ColorChannel, FALSE);
2494                           curColor = ColorChannel;
2495                         }
2496                         break;
2497                       case 5:
2498                         curColor = ColorNormal;
2499                         break;
2500                       }
2501                     }
2502                     if (started == STARTED_NONE && appData.autoComment &&
2503                         (gameMode == IcsObserving ||
2504                          gameMode == IcsPlayingWhite ||
2505                          gameMode == IcsPlayingBlack)) {
2506                       parse_pos = i - oldi;
2507                       memcpy(parse, &buf[oldi], parse_pos);
2508                       parse[parse_pos] = NULLCHAR;
2509                       started = STARTED_COMMENT;
2510                       savingComment = TRUE;
2511                     } else {
2512                       started = STARTED_CHATTER;
2513                       savingComment = FALSE;
2514                     }
2515                     loggedOn = TRUE;
2516                     continue;
2517                   }
2518                 }
2519
2520                 if (looking_at(buf, &i, "* s-shouts: ") ||
2521                     looking_at(buf, &i, "* c-shouts: ")) {
2522                     if (appData.colorize) {
2523                         if (oldi > next_out) {
2524                             SendToPlayer(&buf[next_out], oldi - next_out);
2525                             next_out = oldi;
2526                         }
2527                         Colorize(ColorSShout, FALSE);
2528                         curColor = ColorSShout;
2529                     }
2530                     loggedOn = TRUE;
2531                     started = STARTED_CHATTER;
2532                     continue;
2533                 }
2534
2535                 if (looking_at(buf, &i, "--->")) {
2536                     loggedOn = TRUE;
2537                     continue;
2538                 }
2539
2540                 if (looking_at(buf, &i, "* shouts: ") ||
2541                     looking_at(buf, &i, "--> ")) {
2542                     if (appData.colorize) {
2543                         if (oldi > next_out) {
2544                             SendToPlayer(&buf[next_out], oldi - next_out);
2545                             next_out = oldi;
2546                         }
2547                         Colorize(ColorShout, FALSE);
2548                         curColor = ColorShout;
2549                     }
2550                     loggedOn = TRUE;
2551                     started = STARTED_CHATTER;
2552                     continue;
2553                 }
2554
2555                 if (looking_at( buf, &i, "Challenge:")) {
2556                     if (appData.colorize) {
2557                         if (oldi > next_out) {
2558                             SendToPlayer(&buf[next_out], oldi - next_out);
2559                             next_out = oldi;
2560                         }
2561                         Colorize(ColorChallenge, FALSE);
2562                         curColor = ColorChallenge;
2563                     }
2564                     loggedOn = TRUE;
2565                     continue;
2566                 }
2567
2568                 if (looking_at(buf, &i, "* offers you") ||
2569                     looking_at(buf, &i, "* offers to be") ||
2570                     looking_at(buf, &i, "* would like to") ||
2571                     looking_at(buf, &i, "* requests to") ||
2572                     looking_at(buf, &i, "Your opponent offers") ||
2573                     looking_at(buf, &i, "Your opponent requests")) {
2574
2575                     if (appData.colorize) {
2576                         if (oldi > next_out) {
2577                             SendToPlayer(&buf[next_out], oldi - next_out);
2578                             next_out = oldi;
2579                         }
2580                         Colorize(ColorRequest, FALSE);
2581                         curColor = ColorRequest;
2582                     }
2583                     continue;
2584                 }
2585
2586                 if (looking_at(buf, &i, "* (*) seeking")) {
2587                     if (appData.colorize) {
2588                         if (oldi > next_out) {
2589                             SendToPlayer(&buf[next_out], oldi - next_out);
2590                             next_out = oldi;
2591                         }
2592                         Colorize(ColorSeek, FALSE);
2593                         curColor = ColorSeek;
2594                     }
2595                     continue;
2596             }
2597
2598             if (looking_at(buf, &i, "\\   ")) {
2599                 if (prevColor != ColorNormal) {
2600                     if (oldi > next_out) {
2601                         SendToPlayer(&buf[next_out], oldi - next_out);
2602                         next_out = oldi;
2603                     }
2604                     Colorize(prevColor, TRUE);
2605                     curColor = prevColor;
2606                 }
2607                 if (savingComment) {
2608                     parse_pos = i - oldi;
2609                     memcpy(parse, &buf[oldi], parse_pos);
2610                     parse[parse_pos] = NULLCHAR;
2611                     started = STARTED_COMMENT;
2612                 } else {
2613                     started = STARTED_CHATTER;
2614                 }
2615                 continue;
2616             }
2617
2618             if (looking_at(buf, &i, "Black Strength :") ||
2619                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2620                 looking_at(buf, &i, "<10>") ||
2621                 looking_at(buf, &i, "#@#")) {
2622                 /* Wrong board style */
2623                 loggedOn = TRUE;
2624                 SendToICS(ics_prefix);
2625                 SendToICS("set style 12\n");
2626                 SendToICS(ics_prefix);
2627                 SendToICS("refresh\n");
2628                 continue;
2629             }
2630             
2631             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2632                 ICSInitScript();
2633                 have_sent_ICS_logon = 1;
2634                 continue;
2635             }
2636               
2637             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2638                 (looking_at(buf, &i, "\n<12> ") ||
2639                  looking_at(buf, &i, "<12> "))) {
2640                 loggedOn = TRUE;
2641                 if (oldi > next_out) {
2642                     SendToPlayer(&buf[next_out], oldi - next_out);
2643                 }
2644                 next_out = i;
2645                 started = STARTED_BOARD;
2646                 parse_pos = 0;
2647                 continue;
2648             }
2649
2650             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2651                 looking_at(buf, &i, "<b1> ")) {
2652                 if (oldi > next_out) {
2653                     SendToPlayer(&buf[next_out], oldi - next_out);
2654                 }
2655                 next_out = i;
2656                 started = STARTED_HOLDINGS;
2657                 parse_pos = 0;
2658                 continue;
2659             }
2660
2661             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2662                 loggedOn = TRUE;
2663                 /* Header for a move list -- first line */
2664
2665                 switch (ics_getting_history) {
2666                   case H_FALSE:
2667                     switch (gameMode) {
2668                       case IcsIdle:
2669                       case BeginningOfGame:
2670                         /* User typed "moves" or "oldmoves" while we
2671                            were idle.  Pretend we asked for these
2672                            moves and soak them up so user can step
2673                            through them and/or save them.
2674                            */
2675                         Reset(FALSE, TRUE);
2676                         gameMode = IcsObserving;
2677                         ModeHighlight();
2678                         ics_gamenum = -1;
2679                         ics_getting_history = H_GOT_UNREQ_HEADER;
2680                         break;
2681                       case EditGame: /*?*/
2682                       case EditPosition: /*?*/
2683                         /* Should above feature work in these modes too? */
2684                         /* For now it doesn't */
2685                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2686                         break;
2687                       default:
2688                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2689                         break;
2690                     }
2691                     break;
2692                   case H_REQUESTED:
2693                     /* Is this the right one? */
2694                     if (gameInfo.white && gameInfo.black &&
2695                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2696                         strcmp(gameInfo.black, star_match[2]) == 0) {
2697                         /* All is well */
2698                         ics_getting_history = H_GOT_REQ_HEADER;
2699                     }
2700                     break;
2701                   case H_GOT_REQ_HEADER:
2702                   case H_GOT_UNREQ_HEADER:
2703                   case H_GOT_UNWANTED_HEADER:
2704                   case H_GETTING_MOVES:
2705                     /* Should not happen */
2706                     DisplayError(_("Error gathering move list: two headers"), 0);
2707                     ics_getting_history = H_FALSE;
2708                     break;
2709                 }
2710
2711                 /* Save player ratings into gameInfo if needed */
2712                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2713                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2714                     (gameInfo.whiteRating == -1 ||
2715                      gameInfo.blackRating == -1)) {
2716
2717                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2718                     gameInfo.blackRating = string_to_rating(star_match[3]);
2719                     if (appData.debugMode)
2720                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2721                               gameInfo.whiteRating, gameInfo.blackRating);
2722                 }
2723                 continue;
2724             }
2725
2726             if (looking_at(buf, &i,
2727               "* * match, initial time: * minute*, increment: * second")) {
2728                 /* Header for a move list -- second line */
2729                 /* Initial board will follow if this is a wild game */
2730                 if (gameInfo.event != NULL) free(gameInfo.event);
2731                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2732                 gameInfo.event = StrSave(str);
2733                 /* [HGM] we switched variant. Translate boards if needed. */
2734                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2735                 continue;
2736             }
2737
2738             if (looking_at(buf, &i, "Move  ")) {
2739                 /* Beginning of a move list */
2740                 switch (ics_getting_history) {
2741                   case H_FALSE:
2742                     /* Normally should not happen */
2743                     /* Maybe user hit reset while we were parsing */
2744                     break;
2745                   case H_REQUESTED:
2746                     /* Happens if we are ignoring a move list that is not
2747                      * the one we just requested.  Common if the user
2748                      * tries to observe two games without turning off
2749                      * getMoveList */
2750                     break;
2751                   case H_GETTING_MOVES:
2752                     /* Should not happen */
2753                     DisplayError(_("Error gathering move list: nested"), 0);
2754                     ics_getting_history = H_FALSE;
2755                     break;
2756                   case H_GOT_REQ_HEADER:
2757                     ics_getting_history = H_GETTING_MOVES;
2758                     started = STARTED_MOVES;
2759                     parse_pos = 0;
2760                     if (oldi > next_out) {
2761                         SendToPlayer(&buf[next_out], oldi - next_out);
2762                     }
2763                     break;
2764                   case H_GOT_UNREQ_HEADER:
2765                     ics_getting_history = H_GETTING_MOVES;
2766                     started = STARTED_MOVES_NOHIDE;
2767                     parse_pos = 0;
2768                     break;
2769                   case H_GOT_UNWANTED_HEADER:
2770                     ics_getting_history = H_FALSE;
2771                     break;
2772                 }
2773                 continue;
2774             }                           
2775             
2776             if (looking_at(buf, &i, "% ") ||
2777                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2778                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2779                 savingComment = FALSE;
2780                 switch (started) {
2781                   case STARTED_MOVES:
2782                   case STARTED_MOVES_NOHIDE:
2783                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2784                     parse[parse_pos + i - oldi] = NULLCHAR;
2785                     ParseGameHistory(parse);
2786 #if ZIPPY
2787                     if (appData.zippyPlay && first.initDone) {
2788                         FeedMovesToProgram(&first, forwardMostMove);
2789                         if (gameMode == IcsPlayingWhite) {
2790                             if (WhiteOnMove(forwardMostMove)) {
2791                                 if (first.sendTime) {
2792                                   if (first.useColors) {
2793                                     SendToProgram("black\n", &first); 
2794                                   }
2795                                   SendTimeRemaining(&first, TRUE);
2796                                 }
2797                                 if (first.useColors) {
2798                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2799                                 }
2800                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2801                                 first.maybeThinking = TRUE;
2802                             } else {
2803                                 if (first.usePlayother) {
2804                                   if (first.sendTime) {
2805                                     SendTimeRemaining(&first, TRUE);
2806                                   }
2807                                   SendToProgram("playother\n", &first);
2808                                   firstMove = FALSE;
2809                                 } else {
2810                                   firstMove = TRUE;
2811                                 }
2812                             }
2813                         } else if (gameMode == IcsPlayingBlack) {
2814                             if (!WhiteOnMove(forwardMostMove)) {
2815                                 if (first.sendTime) {
2816                                   if (first.useColors) {
2817                                     SendToProgram("white\n", &first);
2818                                   }
2819                                   SendTimeRemaining(&first, FALSE);
2820                                 }
2821                                 if (first.useColors) {
2822                                   SendToProgram("black\n", &first);
2823                                 }
2824                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2825                                 first.maybeThinking = TRUE;
2826                             } else {
2827                                 if (first.usePlayother) {
2828                                   if (first.sendTime) {
2829                                     SendTimeRemaining(&first, FALSE);
2830                                   }
2831                                   SendToProgram("playother\n", &first);
2832                                   firstMove = FALSE;
2833                                 } else {
2834                                   firstMove = TRUE;
2835                                 }
2836                             }
2837                         }                       
2838                     }
2839 #endif
2840                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2841                         /* Moves came from oldmoves or moves command
2842                            while we weren't doing anything else.
2843                            */
2844                         currentMove = forwardMostMove;
2845                         ClearHighlights();/*!!could figure this out*/
2846                         flipView = appData.flipView;
2847                         DrawPosition(FALSE, boards[currentMove]);
2848                         DisplayBothClocks();
2849                         sprintf(str, "%s vs. %s",
2850                                 gameInfo.white, gameInfo.black);
2851                         DisplayTitle(str);
2852                         gameMode = IcsIdle;
2853                     } else {
2854                         /* Moves were history of an active game */
2855                         if (gameInfo.resultDetails != NULL) {
2856                             free(gameInfo.resultDetails);
2857                             gameInfo.resultDetails = NULL;
2858                         }
2859                     }
2860                     HistorySet(parseList, backwardMostMove,
2861                                forwardMostMove, currentMove-1);
2862                     DisplayMove(currentMove - 1);
2863                     if (started == STARTED_MOVES) next_out = i;
2864                     started = STARTED_NONE;
2865                     ics_getting_history = H_FALSE;
2866                     break;
2867
2868                   case STARTED_OBSERVE:
2869                     started = STARTED_NONE;
2870                     SendToICS(ics_prefix);
2871                     SendToICS("refresh\n");
2872                     break;
2873
2874                   default:
2875                     break;
2876                 }
2877                 if(bookHit) { // [HGM] book: simulate book reply
2878                     static char bookMove[MSG_SIZ]; // a bit generous?
2879
2880                     programStats.nodes = programStats.depth = programStats.time = 
2881                     programStats.score = programStats.got_only_move = 0;
2882                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2883
2884                     strcpy(bookMove, "move ");
2885                     strcat(bookMove, bookHit);
2886                     HandleMachineMove(bookMove, &first);
2887                 }
2888                 continue;
2889             }
2890             
2891             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2892                  started == STARTED_HOLDINGS ||
2893                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2894                 /* Accumulate characters in move list or board */
2895                 parse[parse_pos++] = buf[i];
2896             }
2897             
2898             /* Start of game messages.  Mostly we detect start of game
2899                when the first board image arrives.  On some versions
2900                of the ICS, though, we need to do a "refresh" after starting
2901                to observe in order to get the current board right away. */
2902             if (looking_at(buf, &i, "Adding game * to observation list")) {
2903                 started = STARTED_OBSERVE;
2904                 continue;
2905             }
2906
2907             /* Handle auto-observe */
2908             if (appData.autoObserve &&
2909                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2910                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2911                 char *player;
2912                 /* Choose the player that was highlighted, if any. */
2913                 if (star_match[0][0] == '\033' ||
2914                     star_match[1][0] != '\033') {
2915                     player = star_match[0];
2916                 } else {
2917                     player = star_match[2];
2918                 }
2919                 sprintf(str, "%sobserve %s\n",
2920                         ics_prefix, StripHighlightAndTitle(player));
2921                 SendToICS(str);
2922
2923                 /* Save ratings from notify string */
2924                 strcpy(player1Name, star_match[0]);
2925                 player1Rating = string_to_rating(star_match[1]);
2926                 strcpy(player2Name, star_match[2]);
2927                 player2Rating = string_to_rating(star_match[3]);
2928
2929                 if (appData.debugMode)
2930                   fprintf(debugFP, 
2931                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2932                           player1Name, player1Rating,
2933                           player2Name, player2Rating);
2934
2935                 continue;
2936             }
2937
2938             /* Deal with automatic examine mode after a game,
2939                and with IcsObserving -> IcsExamining transition */
2940             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2941                 looking_at(buf, &i, "has made you an examiner of game *")) {
2942
2943                 int gamenum = atoi(star_match[0]);
2944                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2945                     gamenum == ics_gamenum) {
2946                     /* We were already playing or observing this game;
2947                        no need to refetch history */
2948                     gameMode = IcsExamining;
2949                     if (pausing) {
2950                         pauseExamForwardMostMove = forwardMostMove;
2951                     } else if (currentMove < forwardMostMove) {
2952                         ForwardInner(forwardMostMove);
2953                     }
2954                 } else {
2955                     /* I don't think this case really can happen */
2956                     SendToICS(ics_prefix);
2957                     SendToICS("refresh\n");
2958                 }
2959                 continue;
2960             }    
2961             
2962             /* Error messages */
2963 //          if (ics_user_moved) {
2964             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2965                 if (looking_at(buf, &i, "Illegal move") ||
2966                     looking_at(buf, &i, "Not a legal move") ||
2967                     looking_at(buf, &i, "Your king is in check") ||
2968                     looking_at(buf, &i, "It isn't your turn") ||
2969                     looking_at(buf, &i, "It is not your move")) {
2970                     /* Illegal move */
2971                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2972                         currentMove = --forwardMostMove;
2973                         DisplayMove(currentMove - 1); /* before DMError */
2974                         DrawPosition(FALSE, boards[currentMove]);
2975                         SwitchClocks();
2976                         DisplayBothClocks();
2977                     }
2978                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2979                     ics_user_moved = 0;
2980                     continue;
2981                 }
2982             }
2983
2984             if (looking_at(buf, &i, "still have time") ||
2985                 looking_at(buf, &i, "not out of time") ||
2986                 looking_at(buf, &i, "either player is out of time") ||
2987                 looking_at(buf, &i, "has timeseal; checking")) {
2988                 /* We must have called his flag a little too soon */
2989                 whiteFlag = blackFlag = FALSE;
2990                 continue;
2991             }
2992
2993             if (looking_at(buf, &i, "added * seconds to") ||
2994                 looking_at(buf, &i, "seconds were added to")) {
2995                 /* Update the clocks */
2996                 SendToICS(ics_prefix);
2997                 SendToICS("refresh\n");
2998                 continue;
2999             }
3000
3001             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3002                 ics_clock_paused = TRUE;
3003                 StopClocks();
3004                 continue;
3005             }
3006
3007             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3008                 ics_clock_paused = FALSE;
3009                 StartClocks();
3010                 continue;
3011             }
3012
3013             /* Grab player ratings from the Creating: message.
3014                Note we have to check for the special case when
3015                the ICS inserts things like [white] or [black]. */
3016             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3017                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3018                 /* star_matches:
3019                    0    player 1 name (not necessarily white)
3020                    1    player 1 rating
3021                    2    empty, white, or black (IGNORED)
3022                    3    player 2 name (not necessarily black)
3023                    4    player 2 rating
3024                    
3025                    The names/ratings are sorted out when the game
3026                    actually starts (below).
3027                 */
3028                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3029                 player1Rating = string_to_rating(star_match[1]);
3030                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3031                 player2Rating = string_to_rating(star_match[4]);
3032
3033                 if (appData.debugMode)
3034                   fprintf(debugFP, 
3035                           "Ratings from 'Creating:' %s %d, %s %d\n",
3036                           player1Name, player1Rating,
3037                           player2Name, player2Rating);
3038
3039                 continue;
3040             }
3041             
3042             /* Improved generic start/end-of-game messages */
3043             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3044                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3045                 /* If tkind == 0: */
3046                 /* star_match[0] is the game number */
3047                 /*           [1] is the white player's name */
3048                 /*           [2] is the black player's name */
3049                 /* For end-of-game: */
3050                 /*           [3] is the reason for the game end */
3051                 /*           [4] is a PGN end game-token, preceded by " " */
3052                 /* For start-of-game: */
3053                 /*           [3] begins with "Creating" or "Continuing" */
3054                 /*           [4] is " *" or empty (don't care). */
3055                 int gamenum = atoi(star_match[0]);
3056                 char *whitename, *blackname, *why, *endtoken;
3057                 ChessMove endtype = (ChessMove) 0;
3058
3059                 if (tkind == 0) {
3060                   whitename = star_match[1];
3061                   blackname = star_match[2];
3062                   why = star_match[3];
3063                   endtoken = star_match[4];
3064                 } else {
3065                   whitename = star_match[1];
3066                   blackname = star_match[3];
3067                   why = star_match[5];
3068                   endtoken = star_match[6];
3069                 }
3070
3071                 /* Game start messages */
3072                 if (strncmp(why, "Creating ", 9) == 0 ||
3073                     strncmp(why, "Continuing ", 11) == 0) {
3074                     gs_gamenum = gamenum;
3075                     strcpy(gs_kind, strchr(why, ' ') + 1);
3076 #if ZIPPY
3077                     if (appData.zippyPlay) {
3078                         ZippyGameStart(whitename, blackname);
3079                     }
3080 #endif /*ZIPPY*/
3081                     continue;
3082                 }
3083
3084                 /* Game end messages */
3085                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3086                     ics_gamenum != gamenum) {
3087                     continue;
3088                 }
3089                 while (endtoken[0] == ' ') endtoken++;
3090                 switch (endtoken[0]) {
3091                   case '*':
3092                   default:
3093                     endtype = GameUnfinished;
3094                     break;
3095                   case '0':
3096                     endtype = BlackWins;
3097                     break;
3098                   case '1':
3099                     if (endtoken[1] == '/')
3100                       endtype = GameIsDrawn;
3101                     else
3102                       endtype = WhiteWins;
3103                     break;
3104                 }
3105                 GameEnds(endtype, why, GE_ICS);
3106 #if ZIPPY
3107                 if (appData.zippyPlay && first.initDone) {
3108                     ZippyGameEnd(endtype, why);
3109                     if (first.pr == NULL) {
3110                       /* Start the next process early so that we'll
3111                          be ready for the next challenge */
3112                       StartChessProgram(&first);
3113                     }
3114                     /* Send "new" early, in case this command takes
3115                        a long time to finish, so that we'll be ready
3116                        for the next challenge. */
3117                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3118                     Reset(TRUE, TRUE);
3119                 }
3120 #endif /*ZIPPY*/
3121                 continue;
3122             }
3123
3124             if (looking_at(buf, &i, "Removing game * from observation") ||
3125                 looking_at(buf, &i, "no longer observing game *") ||
3126                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3127                 if (gameMode == IcsObserving &&
3128                     atoi(star_match[0]) == ics_gamenum)
3129                   {
3130                       /* icsEngineAnalyze */
3131                       if (appData.icsEngineAnalyze) {
3132                             ExitAnalyzeMode();
3133                             ModeHighlight();
3134                       }
3135                       StopClocks();
3136                       gameMode = IcsIdle;
3137                       ics_gamenum = -1;
3138                       ics_user_moved = FALSE;
3139                   }
3140                 continue;
3141             }
3142
3143             if (looking_at(buf, &i, "no longer examining game *")) {
3144                 if (gameMode == IcsExamining &&
3145                     atoi(star_match[0]) == ics_gamenum)
3146                   {
3147                       gameMode = IcsIdle;
3148                       ics_gamenum = -1;
3149                       ics_user_moved = FALSE;
3150                   }
3151                 continue;
3152             }
3153
3154             /* Advance leftover_start past any newlines we find,
3155                so only partial lines can get reparsed */
3156             if (looking_at(buf, &i, "\n")) {
3157                 prevColor = curColor;
3158                 if (curColor != ColorNormal) {
3159                     if (oldi > next_out) {
3160                         SendToPlayer(&buf[next_out], oldi - next_out);
3161                         next_out = oldi;
3162                     }
3163                     Colorize(ColorNormal, FALSE);
3164                     curColor = ColorNormal;
3165                 }
3166                 if (started == STARTED_BOARD) {
3167                     started = STARTED_NONE;
3168                     parse[parse_pos] = NULLCHAR;
3169                     ParseBoard12(parse);
3170                     ics_user_moved = 0;
3171
3172                     /* Send premove here */
3173                     if (appData.premove) {
3174                       char str[MSG_SIZ];
3175                       if (currentMove == 0 &&
3176                           gameMode == IcsPlayingWhite &&
3177                           appData.premoveWhite) {
3178                         sprintf(str, "%s%s\n", ics_prefix,
3179                                 appData.premoveWhiteText);
3180                         if (appData.debugMode)
3181                           fprintf(debugFP, "Sending premove:\n");
3182                         SendToICS(str);
3183                       } else if (currentMove == 1 &&
3184                                  gameMode == IcsPlayingBlack &&
3185                                  appData.premoveBlack) {
3186                         sprintf(str, "%s%s\n", ics_prefix,
3187                                 appData.premoveBlackText);
3188                         if (appData.debugMode)
3189                           fprintf(debugFP, "Sending premove:\n");
3190                         SendToICS(str);
3191                       } else if (gotPremove) {
3192                         gotPremove = 0;
3193                         ClearPremoveHighlights();
3194                         if (appData.debugMode)
3195                           fprintf(debugFP, "Sending premove:\n");
3196                           UserMoveEvent(premoveFromX, premoveFromY, 
3197                                         premoveToX, premoveToY, 
3198                                         premovePromoChar);
3199                       }
3200                     }
3201
3202                     /* Usually suppress following prompt */
3203                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3204                         if (looking_at(buf, &i, "*% ")) {
3205                             savingComment = FALSE;
3206                         }
3207                     }
3208                     next_out = i;
3209                 } else if (started == STARTED_HOLDINGS) {
3210                     int gamenum;
3211                     char new_piece[MSG_SIZ];
3212                     started = STARTED_NONE;
3213                     parse[parse_pos] = NULLCHAR;
3214                     if (appData.debugMode)
3215                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3216                                                         parse, currentMove);
3217                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3218                         gamenum == ics_gamenum) {
3219                         if (gameInfo.variant == VariantNormal) {
3220                           /* [HGM] We seem to switch variant during a game!
3221                            * Presumably no holdings were displayed, so we have
3222                            * to move the position two files to the right to
3223                            * create room for them!
3224                            */
3225                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3226                           /* Get a move list just to see the header, which
3227                              will tell us whether this is really bug or zh */
3228                           if (ics_getting_history == H_FALSE) {
3229                             ics_getting_history = H_REQUESTED;
3230                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3231                             SendToICS(str);
3232                           }
3233                         }
3234                         new_piece[0] = NULLCHAR;
3235                         sscanf(parse, "game %d white [%s black [%s <- %s",
3236                                &gamenum, white_holding, black_holding,
3237                                new_piece);
3238                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3239                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3240                         /* [HGM] copy holdings to board holdings area */
3241                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3242                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3243 #if ZIPPY
3244                         if (appData.zippyPlay && first.initDone) {
3245                             ZippyHoldings(white_holding, black_holding,
3246                                           new_piece);
3247                         }
3248 #endif /*ZIPPY*/
3249                         if (tinyLayout || smallLayout) {
3250                             char wh[16], bh[16];
3251                             PackHolding(wh, white_holding);
3252                             PackHolding(bh, black_holding);
3253                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3254                                     gameInfo.white, gameInfo.black);
3255                         } else {
3256                             sprintf(str, "%s [%s] vs. %s [%s]",
3257                                     gameInfo.white, white_holding,
3258                                     gameInfo.black, black_holding);
3259                         }
3260
3261                         DrawPosition(FALSE, boards[currentMove]);
3262                         DisplayTitle(str);
3263                     }
3264                     /* Suppress following prompt */
3265                     if (looking_at(buf, &i, "*% ")) {
3266                         savingComment = FALSE;
3267                     }
3268                     next_out = i;
3269                 }
3270                 continue;
3271             }
3272
3273             i++;                /* skip unparsed character and loop back */
3274         }
3275         
3276         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3277             started != STARTED_HOLDINGS && i > next_out) {
3278             SendToPlayer(&buf[next_out], i - next_out);
3279             next_out = i;
3280         }
3281         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3282         
3283         leftover_len = buf_len - leftover_start;
3284         /* if buffer ends with something we couldn't parse,
3285            reparse it after appending the next read */
3286         
3287     } else if (count == 0) {
3288         RemoveInputSource(isr);
3289         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3290     } else {
3291         DisplayFatalError(_("Error reading from ICS"), error, 1);
3292     }
3293 }
3294
3295
3296 /* Board style 12 looks like this:
3297    
3298    <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
3299    
3300  * The "<12> " is stripped before it gets to this routine.  The two
3301  * trailing 0's (flip state and clock ticking) are later addition, and
3302  * some chess servers may not have them, or may have only the first.
3303  * Additional trailing fields may be added in the future.  
3304  */
3305
3306 #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"
3307
3308 #define RELATION_OBSERVING_PLAYED    0
3309 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3310 #define RELATION_PLAYING_MYMOVE      1
3311 #define RELATION_PLAYING_NOTMYMOVE  -1
3312 #define RELATION_EXAMINING           2
3313 #define RELATION_ISOLATED_BOARD     -3
3314 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3315
3316 void
3317 ParseBoard12(string)
3318      char *string;
3319
3320     GameMode newGameMode;
3321     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3322     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3323     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3324     char to_play, board_chars[200];
3325     char move_str[500], str[500], elapsed_time[500];
3326     char black[32], white[32];
3327     Board board;
3328     int prevMove = currentMove;
3329     int ticking = 2;
3330     ChessMove moveType;
3331     int fromX, fromY, toX, toY;
3332     char promoChar;
3333     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3334     char *bookHit = NULL; // [HGM] book
3335
3336     fromX = fromY = toX = toY = -1;
3337     
3338     newGame = FALSE;
3339
3340     if (appData.debugMode)
3341       fprintf(debugFP, _("Parsing board: %s\n"), string);
3342
3343     move_str[0] = NULLCHAR;
3344     elapsed_time[0] = NULLCHAR;
3345     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3346         int  i = 0, j;
3347         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3348             if(string[i] == ' ') { ranks++; files = 0; }
3349             else files++;
3350             i++;
3351         }
3352         for(j = 0; j <i; j++) board_chars[j] = string[j];
3353         board_chars[i] = '\0';
3354         string += i + 1;
3355     }
3356     n = sscanf(string, PATTERN, &to_play, &double_push,
3357                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3358                &gamenum, white, black, &relation, &basetime, &increment,
3359                &white_stren, &black_stren, &white_time, &black_time,
3360                &moveNum, str, elapsed_time, move_str, &ics_flip,
3361                &ticking);
3362
3363     if (n < 21) {
3364         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3365         DisplayError(str, 0);
3366         return;
3367     }
3368
3369     /* Convert the move number to internal form */
3370     moveNum = (moveNum - 1) * 2;
3371     if (to_play == 'B') moveNum++;
3372     if (moveNum >= MAX_MOVES) {
3373       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3374                         0, 1);
3375       return;
3376     }
3377     
3378     switch (relation) {
3379       case RELATION_OBSERVING_PLAYED:
3380       case RELATION_OBSERVING_STATIC:
3381         if (gamenum == -1) {
3382             /* Old ICC buglet */
3383             relation = RELATION_OBSERVING_STATIC;
3384         }
3385         newGameMode = IcsObserving;
3386         break;
3387       case RELATION_PLAYING_MYMOVE:
3388       case RELATION_PLAYING_NOTMYMOVE:
3389         newGameMode =
3390           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3391             IcsPlayingWhite : IcsPlayingBlack;
3392         break;
3393       case RELATION_EXAMINING:
3394         newGameMode = IcsExamining;
3395         break;
3396       case RELATION_ISOLATED_BOARD:
3397       default:
3398         /* Just display this board.  If user was doing something else,
3399            we will forget about it until the next board comes. */ 
3400         newGameMode = IcsIdle;
3401         break;
3402       case RELATION_STARTING_POSITION:
3403         newGameMode = gameMode;
3404         break;
3405     }
3406     
3407     /* Modify behavior for initial board display on move listing
3408        of wild games.
3409        */
3410     switch (ics_getting_history) {
3411       case H_FALSE:
3412       case H_REQUESTED:
3413         break;
3414       case H_GOT_REQ_HEADER:
3415       case H_GOT_UNREQ_HEADER:
3416         /* This is the initial position of the current game */
3417         gamenum = ics_gamenum;
3418         moveNum = 0;            /* old ICS bug workaround */
3419         if (to_play == 'B') {
3420           startedFromSetupPosition = TRUE;
3421           blackPlaysFirst = TRUE;
3422           moveNum = 1;
3423           if (forwardMostMove == 0) forwardMostMove = 1;
3424           if (backwardMostMove == 0) backwardMostMove = 1;
3425           if (currentMove == 0) currentMove = 1;
3426         }
3427         newGameMode = gameMode;
3428         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3429         break;
3430       case H_GOT_UNWANTED_HEADER:
3431         /* This is an initial board that we don't want */
3432         return;
3433       case H_GETTING_MOVES:
3434         /* Should not happen */
3435         DisplayError(_("Error gathering move list: extra board"), 0);
3436         ics_getting_history = H_FALSE;
3437         return;
3438     }
3439     
3440     /* Take action if this is the first board of a new game, or of a
3441        different game than is currently being displayed.  */
3442     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3443         relation == RELATION_ISOLATED_BOARD) {
3444         
3445         /* Forget the old game and get the history (if any) of the new one */
3446         if (gameMode != BeginningOfGame) {
3447           Reset(FALSE, TRUE);
3448         }
3449         newGame = TRUE;
3450         if (appData.autoRaiseBoard) BoardToTop();
3451         prevMove = -3;
3452         if (gamenum == -1) {
3453             newGameMode = IcsIdle;
3454         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3455                    appData.getMoveList) {
3456             /* Need to get game history */
3457             ics_getting_history = H_REQUESTED;
3458             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3459             SendToICS(str);
3460         }
3461         
3462         /* Initially flip the board to have black on the bottom if playing
3463            black or if the ICS flip flag is set, but let the user change
3464            it with the Flip View button. */
3465         flipView = appData.autoFlipView ? 
3466           (newGameMode == IcsPlayingBlack) || ics_flip :
3467           appData.flipView;
3468         
3469         /* Done with values from previous mode; copy in new ones */
3470         gameMode = newGameMode;
3471         ModeHighlight();
3472         ics_gamenum = gamenum;
3473         if (gamenum == gs_gamenum) {
3474             int klen = strlen(gs_kind);
3475             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3476             sprintf(str, "ICS %s", gs_kind);
3477             gameInfo.event = StrSave(str);
3478         } else {
3479             gameInfo.event = StrSave("ICS game");
3480         }
3481         gameInfo.site = StrSave(appData.icsHost);
3482         gameInfo.date = PGNDate();
3483         gameInfo.round = StrSave("-");
3484         gameInfo.white = StrSave(white);
3485         gameInfo.black = StrSave(black);
3486         timeControl = basetime * 60 * 1000;
3487         timeControl_2 = 0;
3488         timeIncrement = increment * 1000;
3489         movesPerSession = 0;
3490         gameInfo.timeControl = TimeControlTagValue();
3491         VariantSwitch(board, StringToVariant(gameInfo.event) );
3492   if (appData.debugMode) {
3493     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3494     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3495     setbuf(debugFP, NULL);
3496   }
3497
3498         gameInfo.outOfBook = NULL;
3499         
3500         /* Do we have the ratings? */
3501         if (strcmp(player1Name, white) == 0 &&
3502             strcmp(player2Name, black) == 0) {
3503             if (appData.debugMode)
3504               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3505                       player1Rating, player2Rating);
3506             gameInfo.whiteRating = player1Rating;
3507             gameInfo.blackRating = player2Rating;
3508         } else if (strcmp(player2Name, white) == 0 &&
3509                    strcmp(player1Name, black) == 0) {
3510             if (appData.debugMode)
3511               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3512                       player2Rating, player1Rating);
3513             gameInfo.whiteRating = player2Rating;
3514             gameInfo.blackRating = player1Rating;
3515         }
3516         player1Name[0] = player2Name[0] = NULLCHAR;
3517
3518         /* Silence shouts if requested */
3519         if (appData.quietPlay &&
3520             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3521             SendToICS(ics_prefix);
3522             SendToICS("set shout 0\n");
3523         }
3524     }
3525     
3526     /* Deal with midgame name changes */
3527     if (!newGame) {
3528         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3529             if (gameInfo.white) free(gameInfo.white);
3530             gameInfo.white = StrSave(white);
3531         }
3532         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3533             if (gameInfo.black) free(gameInfo.black);
3534             gameInfo.black = StrSave(black);
3535         }
3536     }
3537     
3538     /* Throw away game result if anything actually changes in examine mode */
3539     if (gameMode == IcsExamining && !newGame) {
3540         gameInfo.result = GameUnfinished;
3541         if (gameInfo.resultDetails != NULL) {
3542             free(gameInfo.resultDetails);
3543             gameInfo.resultDetails = NULL;
3544         }
3545     }
3546     
3547     /* In pausing && IcsExamining mode, we ignore boards coming
3548        in if they are in a different variation than we are. */
3549     if (pauseExamInvalid) return;
3550     if (pausing && gameMode == IcsExamining) {
3551         if (moveNum <= pauseExamForwardMostMove) {
3552             pauseExamInvalid = TRUE;
3553             forwardMostMove = pauseExamForwardMostMove;
3554             return;
3555         }
3556     }
3557     
3558   if (appData.debugMode) {
3559     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3560   }
3561     /* Parse the board */
3562     for (k = 0; k < ranks; k++) {
3563       for (j = 0; j < files; j++)
3564         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3565       if(gameInfo.holdingsWidth > 1) {
3566            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3567            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3568       }
3569     }
3570     CopyBoard(boards[moveNum], board);
3571     if (moveNum == 0) {
3572         startedFromSetupPosition =
3573           !CompareBoards(board, initialPosition);
3574         if(startedFromSetupPosition)
3575             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3576     }
3577
3578     /* [HGM] Set castling rights. Take the outermost Rooks,
3579        to make it also work for FRC opening positions. Note that board12
3580        is really defective for later FRC positions, as it has no way to
3581        indicate which Rook can castle if they are on the same side of King.
3582        For the initial position we grant rights to the outermost Rooks,
3583        and remember thos rights, and we then copy them on positions
3584        later in an FRC game. This means WB might not recognize castlings with
3585        Rooks that have moved back to their original position as illegal,
3586        but in ICS mode that is not its job anyway.
3587     */
3588     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3589     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3590
3591         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3592             if(board[0][i] == WhiteRook) j = i;
3593         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3594         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3595             if(board[0][i] == WhiteRook) j = i;
3596         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3598             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3599         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3600         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3601             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3602         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3603
3604         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3605         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3606             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3607         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3608             if(board[BOARD_HEIGHT-1][k] == bKing)
3609                 initialRights[5] = castlingRights[moveNum][5] = k;
3610     } else { int r;
3611         r = castlingRights[moveNum][0] = initialRights[0];
3612         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3613         r = castlingRights[moveNum][1] = initialRights[1];
3614         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3615         r = castlingRights[moveNum][3] = initialRights[3];
3616         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3617         r = castlingRights[moveNum][4] = initialRights[4];
3618         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3619         /* wildcastle kludge: always assume King has rights */
3620         r = castlingRights[moveNum][2] = initialRights[2];
3621         r = castlingRights[moveNum][5] = initialRights[5];
3622     }
3623     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3624     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3625
3626     
3627     if (ics_getting_history == H_GOT_REQ_HEADER ||
3628         ics_getting_history == H_GOT_UNREQ_HEADER) {
3629         /* This was an initial position from a move list, not
3630            the current position */
3631         return;
3632     }
3633     
3634     /* Update currentMove and known move number limits */
3635     newMove = newGame || moveNum > forwardMostMove;
3636
3637     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3638     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3639         takeback = forwardMostMove - moveNum;
3640         for (i = 0; i < takeback; i++) {
3641              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3642              SendToProgram("undo\n", &first);
3643         }
3644     }
3645
3646     if (newGame) {
3647         forwardMostMove = backwardMostMove = currentMove = moveNum;
3648         if (gameMode == IcsExamining && moveNum == 0) {
3649           /* Workaround for ICS limitation: we are not told the wild
3650              type when starting to examine a game.  But if we ask for
3651              the move list, the move list header will tell us */
3652             ics_getting_history = H_REQUESTED;
3653             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3654             SendToICS(str);
3655         }
3656     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3657                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3658         forwardMostMove = moveNum;
3659         if (!pausing || currentMove > forwardMostMove)
3660           currentMove = forwardMostMove;
3661     } else {
3662         /* New part of history that is not contiguous with old part */ 
3663         if (pausing && gameMode == IcsExamining) {
3664             pauseExamInvalid = TRUE;
3665             forwardMostMove = pauseExamForwardMostMove;
3666             return;
3667         }
3668         forwardMostMove = backwardMostMove = currentMove = moveNum;
3669         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3670             ics_getting_history = H_REQUESTED;
3671             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3672             SendToICS(str);
3673         }
3674     }
3675     
3676     /* Update the clocks */
3677     if (strchr(elapsed_time, '.')) {
3678       /* Time is in ms */
3679       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3680       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3681     } else {
3682       /* Time is in seconds */
3683       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3684       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3685     }
3686       
3687
3688 #if ZIPPY
3689     if (appData.zippyPlay && newGame &&
3690         gameMode != IcsObserving && gameMode != IcsIdle &&
3691         gameMode != IcsExamining)
3692       ZippyFirstBoard(moveNum, basetime, increment);
3693 #endif
3694     
3695     /* Put the move on the move list, first converting
3696        to canonical algebraic form. */
3697     if (moveNum > 0) {
3698   if (appData.debugMode) {
3699     if (appData.debugMode) { int f = forwardMostMove;
3700         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3701                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3702     }
3703     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3704     fprintf(debugFP, "moveNum = %d\n", moveNum);
3705     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3706     setbuf(debugFP, NULL);
3707   }
3708         if (moveNum <= backwardMostMove) {
3709             /* We don't know what the board looked like before
3710                this move.  Punt. */
3711             strcpy(parseList[moveNum - 1], move_str);
3712             strcat(parseList[moveNum - 1], " ");
3713             strcat(parseList[moveNum - 1], elapsed_time);
3714             moveList[moveNum - 1][0] = NULLCHAR;
3715         } else if (strcmp(move_str, "none") == 0) {
3716             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3717             /* Again, we don't know what the board looked like;
3718                this is really the start of the game. */
3719             parseList[moveNum - 1][0] = NULLCHAR;
3720             moveList[moveNum - 1][0] = NULLCHAR;
3721             backwardMostMove = moveNum;
3722             startedFromSetupPosition = TRUE;
3723             fromX = fromY = toX = toY = -1;
3724         } else {
3725           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3726           //                 So we parse the long-algebraic move string in stead of the SAN move
3727           int valid; char buf[MSG_SIZ], *prom;
3728
3729           // str looks something like "Q/a1-a2"; kill the slash
3730           if(str[1] == '/') 
3731                 sprintf(buf, "%c%s", str[0], str+2);
3732           else  strcpy(buf, str); // might be castling
3733           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3734                 strcat(buf, prom); // long move lacks promo specification!
3735           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3736                 if(appData.debugMode) 
3737                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3738                 strcpy(move_str, buf);
3739           }
3740           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3741                                 &fromX, &fromY, &toX, &toY, &promoChar)
3742                || ParseOneMove(buf, moveNum - 1, &moveType,
3743                                 &fromX, &fromY, &toX, &toY, &promoChar);
3744           // end of long SAN patch
3745           if (valid) {
3746             (void) CoordsToAlgebraic(boards[moveNum - 1],
3747                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3748                                      fromY, fromX, toY, toX, promoChar,
3749                                      parseList[moveNum-1]);
3750             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3751                              castlingRights[moveNum]) ) {
3752               case MT_NONE:
3753               case MT_STALEMATE:
3754               default:
3755                 break;
3756               case MT_CHECK:
3757                 if(gameInfo.variant != VariantShogi)
3758                     strcat(parseList[moveNum - 1], "+");
3759                 break;
3760               case MT_CHECKMATE:
3761               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3762                 strcat(parseList[moveNum - 1], "#");
3763                 break;
3764             }
3765             strcat(parseList[moveNum - 1], " ");
3766             strcat(parseList[moveNum - 1], elapsed_time);
3767             /* currentMoveString is set as a side-effect of ParseOneMove */
3768             strcpy(moveList[moveNum - 1], currentMoveString);
3769             strcat(moveList[moveNum - 1], "\n");
3770           } else {
3771             /* Move from ICS was illegal!?  Punt. */
3772   if (appData.debugMode) {
3773     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3774     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3775   }
3776             strcpy(parseList[moveNum - 1], move_str);
3777             strcat(parseList[moveNum - 1], " ");
3778             strcat(parseList[moveNum - 1], elapsed_time);
3779             moveList[moveNum - 1][0] = NULLCHAR;
3780             fromX = fromY = toX = toY = -1;
3781           }
3782         }
3783   if (appData.debugMode) {
3784     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3785     setbuf(debugFP, NULL);
3786   }
3787
3788 #if ZIPPY
3789         /* Send move to chess program (BEFORE animating it). */
3790         if (appData.zippyPlay && !newGame && newMove && 
3791            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3792
3793             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3794                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3795                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3796                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3797                             move_str);
3798                     DisplayError(str, 0);
3799                 } else {
3800                     if (first.sendTime) {
3801                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3802                     }
3803                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3804                     if (firstMove && !bookHit) {
3805                         firstMove = FALSE;
3806                         if (first.useColors) {
3807                           SendToProgram(gameMode == IcsPlayingWhite ?
3808                                         "white\ngo\n" :
3809                                         "black\ngo\n", &first);
3810                         } else {
3811                           SendToProgram("go\n", &first);
3812                         }
3813                         first.maybeThinking = TRUE;
3814                     }
3815                 }
3816             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3817               if (moveList[moveNum - 1][0] == NULLCHAR) {