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