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