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