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