removed AnalysisPopUp. Use EngineOutputPopUp instead
[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 void DisplayAnalysis P((void));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
242 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
246 int opponentKibitzes;
247 int lastSavedGame; /* [HGM] save: ID of game */
248 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
249 extern int chatCount;
250 int chattingPartner;
251
252 /* States for ics_getting_history */
253 #define H_FALSE 0
254 #define H_REQUESTED 1
255 #define H_GOT_REQ_HEADER 2
256 #define H_GOT_UNREQ_HEADER 3
257 #define H_GETTING_MOVES 4
258 #define H_GOT_UNWANTED_HEADER 5
259
260 /* whosays values for GameEnds */
261 #define GE_ICS 0
262 #define GE_ENGINE 1
263 #define GE_PLAYER 2
264 #define GE_FILE 3
265 #define GE_XBOARD 4
266 #define GE_ENGINE1 5
267 #define GE_ENGINE2 6
268
269 /* Maximum number of games in a cmail message */
270 #define CMAIL_MAX_GAMES 20
271
272 /* Different types of move when calling RegisterMove */
273 #define CMAIL_MOVE   0
274 #define CMAIL_RESIGN 1
275 #define CMAIL_DRAW   2
276 #define CMAIL_ACCEPT 3
277
278 /* Different types of result to remember for each game */
279 #define CMAIL_NOT_RESULT 0
280 #define CMAIL_OLD_RESULT 1
281 #define CMAIL_NEW_RESULT 2
282
283 /* Telnet protocol constants */
284 #define TN_WILL 0373
285 #define TN_WONT 0374
286 #define TN_DO   0375
287 #define TN_DONT 0376
288 #define TN_IAC  0377
289 #define TN_ECHO 0001
290 #define TN_SGA  0003
291 #define TN_PORT 23
292
293 /* [AS] */
294 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 {
296     assert( dst != NULL );
297     assert( src != NULL );
298     assert( count > 0 );
299
300     strncpy( dst, src, count );
301     dst[ count-1 ] = '\0';
302     return dst;
303 }
304
305 /* Some compiler can't cast u64 to double
306  * This function do the job for us:
307
308  * We use the highest bit for cast, this only
309  * works if the highest bit is not
310  * in use (This should not happen)
311  *
312  * We used this for all compiler
313  */
314 double
315 u64ToDouble(u64 value)
316 {
317   double r;
318   u64 tmp = value & u64Const(0x7fffffffffffffff);
319   r = (double)(s64)tmp;
320   if (value & u64Const(0x8000000000000000))
321        r +=  9.2233720368547758080e18; /* 2^63 */
322  return r;
323 }
324
325 /* Fake up flags for now, as we aren't keeping track of castling
326    availability yet. [HGM] Change of logic: the flag now only
327    indicates the type of castlings allowed by the rule of the game.
328    The actual rights themselves are maintained in the array
329    castlingRights, as part of the game history, and are not probed
330    by this function.
331  */
332 int
333 PosFlags(index)
334 {
335   int flags = F_ALL_CASTLE_OK;
336   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
337   switch (gameInfo.variant) {
338   case VariantSuicide:
339     flags &= ~F_ALL_CASTLE_OK;
340   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
341     flags |= F_IGNORE_CHECK;
342   case VariantLosers:
343     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
344     break;
345   case VariantAtomic:
346     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347     break;
348   case VariantKriegspiel:
349     flags |= F_KRIEGSPIEL_CAPTURE;
350     break;
351   case VariantCapaRandom: 
352   case VariantFischeRandom:
353     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
354   case VariantNoCastle:
355   case VariantShatranj:
356   case VariantCourier:
357     flags &= ~F_ALL_CASTLE_OK;
358     break;
359   default:
360     break;
361   }
362   return flags;
363 }
364
365 FILE *gameFileFP, *debugFP;
366
367 /* 
368     [AS] Note: sometimes, the sscanf() function is used to parse the input
369     into a fixed-size buffer. Because of this, we must be prepared to
370     receive strings as long as the size of the input buffer, which is currently
371     set to 4K for Windows and 8K for the rest.
372     So, we must either allocate sufficiently large buffers here, or
373     reduce the size of the input buffer in the input reading part.
374 */
375
376 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
377 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
378 char thinkOutput1[MSG_SIZ*10];
379
380 ChessProgramState first, second;
381
382 /* premove variables */
383 int premoveToX = 0;
384 int premoveToY = 0;
385 int premoveFromX = 0;
386 int premoveFromY = 0;
387 int premovePromoChar = 0;
388 int gotPremove = 0;
389 Boolean alarmSounded;
390 /* end premove variables */
391
392 char *ics_prefix = "$";
393 int ics_type = ICS_GENERIC;
394
395 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
396 int pauseExamForwardMostMove = 0;
397 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
398 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
399 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
400 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
401 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
402 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
403 int whiteFlag = FALSE, blackFlag = FALSE;
404 int userOfferedDraw = FALSE;
405 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
406 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
407 int cmailMoveType[CMAIL_MAX_GAMES];
408 long ics_clock_paused = 0;
409 ProcRef icsPR = NoProc, cmailPR = NoProc;
410 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
411 GameMode gameMode = BeginningOfGame;
412 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
413 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
414 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
415 int hiddenThinkOutputState = 0; /* [AS] */
416 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
417 int adjudicateLossPlies = 6;
418 char white_holding[64], black_holding[64];
419 TimeMark lastNodeCountTime;
420 long lastNodeCount=0;
421 int have_sent_ICS_logon = 0;
422 int movesPerSession;
423 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
424 long timeControl_2; /* [AS] Allow separate time controls */
425 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
426 long timeRemaining[2][MAX_MOVES];
427 int matchGame = 0;
428 TimeMark programStartTime;
429 char ics_handle[MSG_SIZ];
430 int have_set_title = 0;
431
432 /* animateTraining preserves the state of appData.animate
433  * when Training mode is activated. This allows the
434  * response to be animated when appData.animate == TRUE and
435  * appData.animateDragging == TRUE.
436  */
437 Boolean animateTraining;
438
439 GameInfo gameInfo;
440
441 AppData appData;
442
443 Board boards[MAX_MOVES];
444 /* [HGM] Following 7 needed for accurate legality tests: */
445 signed char  epStatus[MAX_MOVES];
446 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
447 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
448 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
449 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int   initialRulePlies, FENrulePlies;
451 char  FENepStatus;
452 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int loadFlag = 0; 
454 int shuffleOpenings;
455 int mute; // mute all sounds
456
457 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
458     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
459         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
460     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
461         BlackKing, BlackBishop, BlackKnight, BlackRook }
462 };
463
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
465     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
466         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
467     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
468         BlackKing, BlackKing, BlackKnight, BlackRook }
469 };
470
471 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
472     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
473         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
474     { BlackRook, BlackMan, BlackBishop, BlackQueen,
475         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
476 };
477
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
479     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
480         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
482         BlackKing, BlackBishop, BlackKnight, BlackRook }
483 };
484
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
486     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
487         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
488     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
489         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
490 };
491
492
493 #if (BOARD_SIZE>=10)
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
495     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
496         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
497     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
498         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
499 };
500
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
503         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
505         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 };
507
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
509     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
510         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
512         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
513 };
514
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
516     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
517         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
518     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
519         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
520 };
521
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
523     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
524         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
525     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
526         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
527 };
528
529 #ifdef GOTHIC
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
532         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
533     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
534         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
535 };
536 #else // !GOTHIC
537 #define GothicArray CapablancaArray
538 #endif // !GOTHIC
539
540 #ifdef FALCON
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
543         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
545         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
546 };
547 #else // !FALCON
548 #define FalconArray CapablancaArray
549 #endif // !FALCON
550
551 #else // !(BOARD_SIZE>=10)
552 #define XiangqiPosition FIDEArray
553 #define CapablancaArray FIDEArray
554 #define GothicArray FIDEArray
555 #define GreatArray FIDEArray
556 #endif // !(BOARD_SIZE>=10)
557
558 #if (BOARD_SIZE>=12)
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
561         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
563         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 };
565 #else // !(BOARD_SIZE>=12)
566 #define CourierArray CapablancaArray
567 #endif // !(BOARD_SIZE>=12)
568
569
570 Board initialPosition;
571
572
573 /* Convert str to a rating. Checks for special cases of "----",
574
575    "++++", etc. Also strips ()'s */
576 int
577 string_to_rating(str)
578   char *str;
579 {
580   while(*str && !isdigit(*str)) ++str;
581   if (!*str)
582     return 0;   /* One of the special "no rating" cases */
583   else
584     return atoi(str);
585 }
586
587 void
588 ClearProgramStats()
589 {
590     /* Init programStats */
591     programStats.movelist[0] = 0;
592     programStats.depth = 0;
593     programStats.nr_moves = 0;
594     programStats.moves_left = 0;
595     programStats.nodes = 0;
596     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
597     programStats.score = 0;
598     programStats.got_only_move = 0;
599     programStats.got_fail = 0;
600     programStats.line_is_book = 0;
601 }
602
603 void
604 InitBackEnd1()
605 {
606     int matched, min, sec;
607
608     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609
610     GetTimeMark(&programStartTime);
611     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
612
613     ClearProgramStats();
614     programStats.ok_to_send = 1;
615     programStats.seen_stat = 0;
616
617     /*
618      * Initialize game list
619      */
620     ListNew(&gameList);
621
622
623     /*
624      * Internet chess server status
625      */
626     if (appData.icsActive) {
627         appData.matchMode = FALSE;
628         appData.matchGames = 0;
629 #if ZIPPY       
630         appData.noChessProgram = !appData.zippyPlay;
631 #else
632         appData.zippyPlay = FALSE;
633         appData.zippyTalk = FALSE;
634         appData.noChessProgram = TRUE;
635 #endif
636         if (*appData.icsHelper != NULLCHAR) {
637             appData.useTelnet = TRUE;
638             appData.telnetProgram = appData.icsHelper;
639         }
640     } else {
641         appData.zippyTalk = appData.zippyPlay = FALSE;
642     }
643
644     /* [AS] Initialize pv info list [HGM] and game state */
645     {
646         int i, j;
647
648         for( i=0; i<MAX_MOVES; i++ ) {
649             pvInfoList[i].depth = -1;
650             epStatus[i]=EP_NONE;
651             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
652         }
653     }
654
655     /*
656      * Parse timeControl resource
657      */
658     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
659                           appData.movesPerSession)) {
660         char buf[MSG_SIZ];
661         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
662         DisplayFatalError(buf, 0, 2);
663     }
664
665     /*
666      * Parse searchTime resource
667      */
668     if (*appData.searchTime != NULLCHAR) {
669         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670         if (matched == 1) {
671             searchTime = min * 60;
672         } else if (matched == 2) {
673             searchTime = min * 60 + sec;
674         } else {
675             char buf[MSG_SIZ];
676             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
677             DisplayFatalError(buf, 0, 2);
678         }
679     }
680
681     /* [AS] Adjudication threshold */
682     adjudicateLossThreshold = appData.adjudicateLossThreshold;
683     
684     first.which = "first";
685     second.which = "second";
686     first.maybeThinking = second.maybeThinking = FALSE;
687     first.pr = second.pr = NoProc;
688     first.isr = second.isr = NULL;
689     first.sendTime = second.sendTime = 2;
690     first.sendDrawOffers = 1;
691     if (appData.firstPlaysBlack) {
692         first.twoMachinesColor = "black\n";
693         second.twoMachinesColor = "white\n";
694     } else {
695         first.twoMachinesColor = "white\n";
696         second.twoMachinesColor = "black\n";
697     }
698     first.program = appData.firstChessProgram;
699     second.program = appData.secondChessProgram;
700     first.host = appData.firstHost;
701     second.host = appData.secondHost;
702     first.dir = appData.firstDirectory;
703     second.dir = appData.secondDirectory;
704     first.other = &second;
705     second.other = &first;
706     first.initString = appData.initString;
707     second.initString = appData.secondInitString;
708     first.computerString = appData.firstComputerString;
709     second.computerString = appData.secondComputerString;
710     first.useSigint = second.useSigint = TRUE;
711     first.useSigterm = second.useSigterm = TRUE;
712     first.reuse = appData.reuseFirst;
713     second.reuse = appData.reuseSecond;
714     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
715     second.nps = appData.secondNPS;
716     first.useSetboard = second.useSetboard = FALSE;
717     first.useSAN = second.useSAN = FALSE;
718     first.usePing = second.usePing = FALSE;
719     first.lastPing = second.lastPing = 0;
720     first.lastPong = second.lastPong = 0;
721     first.usePlayother = second.usePlayother = FALSE;
722     first.useColors = second.useColors = TRUE;
723     first.useUsermove = second.useUsermove = FALSE;
724     first.sendICS = second.sendICS = FALSE;
725     first.sendName = second.sendName = appData.icsActive;
726     first.sdKludge = second.sdKludge = FALSE;
727     first.stKludge = second.stKludge = FALSE;
728     TidyProgramName(first.program, first.host, first.tidy);
729     TidyProgramName(second.program, second.host, second.tidy);
730     first.matchWins = second.matchWins = 0;
731     strcpy(first.variants, appData.variant);
732     strcpy(second.variants, appData.variant);
733     first.analysisSupport = second.analysisSupport = 2; /* detect */
734     first.analyzing = second.analyzing = FALSE;
735     first.initDone = second.initDone = FALSE;
736
737     /* New features added by Tord: */
738     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
739     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
740     /* End of new features added by Tord. */
741     first.fenOverride  = appData.fenOverride1;
742     second.fenOverride = appData.fenOverride2;
743
744     /* [HGM] time odds: set factor for each machine */
745     first.timeOdds  = appData.firstTimeOdds;
746     second.timeOdds = appData.secondTimeOdds;
747     { int norm = 1;
748         if(appData.timeOddsMode) {
749             norm = first.timeOdds;
750             if(norm > second.timeOdds) norm = second.timeOdds;
751         }
752         first.timeOdds /= norm;
753         second.timeOdds /= norm;
754     }
755
756     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
757     first.accumulateTC = appData.firstAccumulateTC;
758     second.accumulateTC = appData.secondAccumulateTC;
759     first.maxNrOfSessions = second.maxNrOfSessions = 1;
760
761     /* [HGM] debug */
762     first.debug = second.debug = FALSE;
763     first.supportsNPS = second.supportsNPS = UNKNOWN;
764
765     /* [HGM] options */
766     first.optionSettings  = appData.firstOptions;
767     second.optionSettings = appData.secondOptions;
768
769     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
770     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
771     first.isUCI = appData.firstIsUCI; /* [AS] */
772     second.isUCI = appData.secondIsUCI; /* [AS] */
773     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
774     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775
776     if (appData.firstProtocolVersion > PROTOVER ||
777         appData.firstProtocolVersion < 1) {
778       char buf[MSG_SIZ];
779       sprintf(buf, _("protocol version %d not supported"),
780               appData.firstProtocolVersion);
781       DisplayFatalError(buf, 0, 2);
782     } else {
783       first.protocolVersion = appData.firstProtocolVersion;
784     }
785
786     if (appData.secondProtocolVersion > PROTOVER ||
787         appData.secondProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.secondProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       second.protocolVersion = appData.secondProtocolVersion;
794     }
795
796     if (appData.icsActive) {
797         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
798     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
799         appData.clockMode = FALSE;
800         first.sendTime = second.sendTime = 0;
801     }
802     
803 #if ZIPPY
804     /* Override some settings from environment variables, for backward
805        compatibility.  Unfortunately it's not feasible to have the env
806        vars just set defaults, at least in xboard.  Ugh.
807     */
808     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
809       ZippyInit();
810     }
811 #endif
812     
813     if (appData.noChessProgram) {
814         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
815         sprintf(programVersion, "%s", PACKAGE_STRING);
816     } else {
817       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
818       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
819       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
820     }
821
822     if (!appData.icsActive) {
823       char buf[MSG_SIZ];
824       /* Check for variants that are supported only in ICS mode,
825          or not at all.  Some that are accepted here nevertheless
826          have bugs; see comments below.
827       */
828       VariantClass variant = StringToVariant(appData.variant);
829       switch (variant) {
830       case VariantBughouse:     /* need four players and two boards */
831       case VariantKriegspiel:   /* need to hide pieces and move details */
832       /* case VariantFischeRandom: (Fabien: moved below) */
833         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
834         DisplayFatalError(buf, 0, 2);
835         return;
836
837       case VariantUnknown:
838       case VariantLoadable:
839       case Variant29:
840       case Variant30:
841       case Variant31:
842       case Variant32:
843       case Variant33:
844       case Variant34:
845       case Variant35:
846       case Variant36:
847       default:
848         sprintf(buf, _("Unknown variant name %s"), appData.variant);
849         DisplayFatalError(buf, 0, 2);
850         return;
851
852       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
853       case VariantFairy:      /* [HGM] TestLegality definitely off! */
854       case VariantGothic:     /* [HGM] should work */
855       case VariantCapablanca: /* [HGM] should work */
856       case VariantCourier:    /* [HGM] initial forced moves not implemented */
857       case VariantShogi:      /* [HGM] drops not tested for legality */
858       case VariantKnightmate: /* [HGM] should work */
859       case VariantCylinder:   /* [HGM] untested */
860       case VariantFalcon:     /* [HGM] untested */
861       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
862                                  offboard interposition not understood */
863       case VariantNormal:     /* definitely works! */
864       case VariantWildCastle: /* pieces not automatically shuffled */
865       case VariantNoCastle:   /* pieces not automatically shuffled */
866       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
867       case VariantLosers:     /* should work except for win condition,
868                                  and doesn't know captures are mandatory */
869       case VariantSuicide:    /* should work except for win condition,
870                                  and doesn't know captures are mandatory */
871       case VariantGiveaway:   /* should work except for win condition,
872                                  and doesn't know captures are mandatory */
873       case VariantTwoKings:   /* should work */
874       case VariantAtomic:     /* should work except for win condition */
875       case Variant3Check:     /* should work except for win condition */
876       case VariantShatranj:   /* should work except for all win conditions */
877       case VariantBerolina:   /* might work if TestLegality is off */
878       case VariantCapaRandom: /* should work */
879       case VariantJanus:      /* should work */
880       case VariantSuper:      /* experimental */
881       case VariantGreat:      /* experimental, requires legality testing to be off */
882         break;
883       }
884     }
885
886     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
887     InitEngineUCI( installDir, &second );
888 }
889
890 int NextIntegerFromString( char ** str, long * value )
891 {
892     int result = -1;
893     char * s = *str;
894
895     while( *s == ' ' || *s == '\t' ) {
896         s++;
897     }
898
899     *value = 0;
900
901     if( *s >= '0' && *s <= '9' ) {
902         while( *s >= '0' && *s <= '9' ) {
903             *value = *value * 10 + (*s - '0');
904             s++;
905         }
906
907         result = 0;
908     }
909
910     *str = s;
911
912     return result;
913 }
914
915 int NextTimeControlFromString( char ** str, long * value )
916 {
917     long temp;
918     int result = NextIntegerFromString( str, &temp );
919
920     if( result == 0 ) {
921         *value = temp * 60; /* Minutes */
922         if( **str == ':' ) {
923             (*str)++;
924             result = NextIntegerFromString( str, &temp );
925             *value += temp; /* Seconds */
926         }
927     }
928
929     return result;
930 }
931
932 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
933 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
934     int result = -1; long temp, temp2;
935
936     if(**str != '+') return -1; // old params remain in force!
937     (*str)++;
938     if( NextTimeControlFromString( str, &temp ) ) return -1;
939
940     if(**str != '/') {
941         /* time only: incremental or sudden-death time control */
942         if(**str == '+') { /* increment follows; read it */
943             (*str)++;
944             if(result = NextIntegerFromString( str, &temp2)) return -1;
945             *inc = temp2 * 1000;
946         } else *inc = 0;
947         *moves = 0; *tc = temp * 1000; 
948         return 0;
949     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
950
951     (*str)++; /* classical time control */
952     result = NextTimeControlFromString( str, &temp2);
953     if(result == 0) {
954         *moves = temp/60;
955         *tc    = temp2 * 1000;
956         *inc   = 0;
957     }
958     return result;
959 }
960
961 int GetTimeQuota(int movenr)
962 {   /* [HGM] get time to add from the multi-session time-control string */
963     int moves=1; /* kludge to force reading of first session */
964     long time, increment;
965     char *s = fullTimeControlString;
966
967     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968     do {
969         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
970         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
971         if(movenr == -1) return time;    /* last move before new session     */
972         if(!moves) return increment;     /* current session is incremental   */
973         if(movenr >= 0) movenr -= moves; /* we already finished this session */
974     } while(movenr >= -1);               /* try again for next session       */
975
976     return 0; // no new time quota on this move
977 }
978
979 int
980 ParseTimeControl(tc, ti, mps)
981      char *tc;
982      int ti;
983      int mps;
984 {
985   long tc1;
986   long tc2;
987   char buf[MSG_SIZ];
988   
989   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
990   if(ti > 0) {
991     if(mps)
992       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
993     else sprintf(buf, "+%s+%d", tc, ti);
994   } else {
995     if(mps)
996              sprintf(buf, "+%d/%s", mps, tc);
997     else sprintf(buf, "+%s", tc);
998   }
999   fullTimeControlString = StrSave(buf);
1000   
1001   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1002     return FALSE;
1003   }
1004   
1005   if( *tc == '/' ) {
1006     /* Parse second time control */
1007     tc++;
1008     
1009     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1010       return FALSE;
1011     }
1012     
1013     if( tc2 == 0 ) {
1014       return FALSE;
1015     }
1016     
1017     timeControl_2 = tc2 * 1000;
1018   }
1019   else {
1020     timeControl_2 = 0;
1021   }
1022   
1023   if( tc1 == 0 ) {
1024     return FALSE;
1025   }
1026   
1027   timeControl = tc1 * 1000;
1028   
1029   if (ti >= 0) {
1030     timeIncrement = ti * 1000;  /* convert to ms */
1031     movesPerSession = 0;
1032   } else {
1033     timeIncrement = 0;
1034     movesPerSession = mps;
1035   }
1036   return TRUE;
1037 }
1038
1039 void
1040 InitBackEnd2()
1041 {
1042     if (appData.debugMode) {
1043         fprintf(debugFP, "%s\n", programVersion);
1044     }
1045
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 int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051     
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     char str[500];
2060     int i, oldi;
2061     int buf_len;
2062     int next_out;
2063     int tkind;
2064     int backup;    /* [DM] For zippy color lines */
2065     char *p;
2066     char talker[MSG_SIZ]; // [HGM] chat
2067     int channel;
2068
2069     if (appData.debugMode) {
2070       if (!error) {
2071         fprintf(debugFP, "<ICS: ");
2072         show_bytes(debugFP, data, count);
2073         fprintf(debugFP, "\n");
2074       }
2075     }
2076
2077     if (appData.debugMode) { int f = forwardMostMove;
2078         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2079                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2080     }
2081     if (count > 0) {
2082         /* If last read ended with a partial line that we couldn't parse,
2083            prepend it to the new read and try again. */
2084         if (leftover_len > 0) {
2085             for (i=0; i<leftover_len; i++)
2086               buf[i] = buf[leftover_start + i];
2087         }
2088
2089         /* Copy in new characters, removing nulls and \r's */
2090         buf_len = leftover_len;
2091         for (i = 0; i < count; i++) {
2092             if (data[i] != NULLCHAR && data[i] != '\r')
2093               buf[buf_len++] = data[i];
2094             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2095                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2096                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2097                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2098                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2099             }
2100         }
2101
2102         buf[buf_len] = NULLCHAR;
2103         next_out = leftover_len;
2104         leftover_start = 0;
2105         
2106         i = 0;
2107         while (i < buf_len) {
2108             /* Deal with part of the TELNET option negotiation
2109                protocol.  We refuse to do anything beyond the
2110                defaults, except that we allow the WILL ECHO option,
2111                which ICS uses to turn off password echoing when we are
2112                directly connected to it.  We reject this option
2113                if localLineEditing mode is on (always on in xboard)
2114                and we are talking to port 23, which might be a real
2115                telnet server that will try to keep WILL ECHO on permanently.
2116              */
2117             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2118                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2119                 unsigned char option;
2120                 oldi = i;
2121                 switch ((unsigned char) buf[++i]) {
2122                   case TN_WILL:
2123                     if (appData.debugMode)
2124                       fprintf(debugFP, "\n<WILL ");
2125                     switch (option = (unsigned char) buf[++i]) {
2126                       case TN_ECHO:
2127                         if (appData.debugMode)
2128                           fprintf(debugFP, "ECHO ");
2129                         /* Reply only if this is a change, according
2130                            to the protocol rules. */
2131                         if (remoteEchoOption) break;
2132                         if (appData.localLineEditing &&
2133                             atoi(appData.icsPort) == TN_PORT) {
2134                             TelnetRequest(TN_DONT, TN_ECHO);
2135                         } else {
2136                             EchoOff();
2137                             TelnetRequest(TN_DO, TN_ECHO);
2138                             remoteEchoOption = TRUE;
2139                         }
2140                         break;
2141                       default:
2142                         if (appData.debugMode)
2143                           fprintf(debugFP, "%d ", option);
2144                         /* Whatever this is, we don't want it. */
2145                         TelnetRequest(TN_DONT, option);
2146                         break;
2147                     }
2148                     break;
2149                   case TN_WONT:
2150                     if (appData.debugMode)
2151                       fprintf(debugFP, "\n<WONT ");
2152                     switch (option = (unsigned char) buf[++i]) {
2153                       case TN_ECHO:
2154                         if (appData.debugMode)
2155                           fprintf(debugFP, "ECHO ");
2156                         /* Reply only if this is a change, according
2157                            to the protocol rules. */
2158                         if (!remoteEchoOption) break;
2159                         EchoOn();
2160                         TelnetRequest(TN_DONT, TN_ECHO);
2161                         remoteEchoOption = FALSE;
2162                         break;
2163                       default:
2164                         if (appData.debugMode)
2165                           fprintf(debugFP, "%d ", (unsigned char) option);
2166                         /* Whatever this is, it must already be turned
2167                            off, because we never agree to turn on
2168                            anything non-default, so according to the
2169                            protocol rules, we don't reply. */
2170                         break;
2171                     }
2172                     break;
2173                   case TN_DO:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<DO ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       default:
2178                         /* Whatever this is, we refuse to do it. */
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "%d ", option);
2181                         TelnetRequest(TN_WONT, option);
2182                         break;
2183                     }
2184                     break;
2185                   case TN_DONT:
2186                     if (appData.debugMode)
2187                       fprintf(debugFP, "\n<DONT ");
2188                     switch (option = (unsigned char) buf[++i]) {
2189                       default:
2190                         if (appData.debugMode)
2191                           fprintf(debugFP, "%d ", option);
2192                         /* Whatever this is, we are already not doing
2193                            it, because we never agree to do anything
2194                            non-default, so according to the protocol
2195                            rules, we don't reply. */
2196                         break;
2197                     }
2198                     break;
2199                   case TN_IAC:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<IAC ");
2202                     /* Doubled IAC; pass it through */
2203                     i--;
2204                     break;
2205                   default:
2206                     if (appData.debugMode)
2207                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2208                     /* Drop all other telnet commands on the floor */
2209                     break;
2210                 }
2211                 if (oldi > next_out)
2212                   SendToPlayer(&buf[next_out], oldi - next_out);
2213                 if (++i > next_out)
2214                   next_out = i;
2215                 continue;
2216             }
2217                 
2218             /* OK, this at least will *usually* work */
2219             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2220                 loggedOn = TRUE;
2221             }
2222             
2223             if (loggedOn && !intfSet) {
2224                 if (ics_type == ICS_ICC) {
2225                   sprintf(str,
2226                           "/set-quietly interface %s\n/set-quietly style 12\n",
2227                           programVersion);
2228           if (!appData.noJoin)
2229               strcat(str, "/set-quietly wrap 0\n");
2230                 } else if (ics_type == ICS_CHESSNET) {
2231                   sprintf(str, "/style 12\n");
2232                 } else {
2233                   strcpy(str, "alias $ @\n$set interface ");
2234                   strcat(str, programVersion);
2235                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2236 #ifdef WIN32
2237                   strcat(str, "$iset nohighlight 1\n");
2238 #endif
2239           if (!appData.noJoin)
2240               strcat(str, "$iset nowrap 1\n");
2241                   strcat(str, "$iset lock 1\n$style 12\n");
2242                 }
2243                 SendToICS(str);
2244                 NotifyFrontendLogin();
2245                 intfSet = TRUE;
2246             }
2247
2248             if (started == STARTED_COMMENT) {
2249                 /* Accumulate characters in comment */
2250                 parse[parse_pos++] = buf[i];
2251                 if (buf[i] == '\n') {
2252                     parse[parse_pos] = NULLCHAR;
2253                     if(chattingPartner>=0) {
2254                         char mess[MSG_SIZ];
2255                         sprintf(mess, "%s%s", talker, parse);
2256                         OutputChatMessage(chattingPartner, mess);
2257                         chattingPartner = -1;
2258                     } else
2259                     if(!suppressKibitz) // [HGM] kibitz
2260                         AppendComment(forwardMostMove, StripHighlight(parse));
2261                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2262                         int nrDigit = 0, nrAlph = 0, i;
2263                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2264                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2265                         parse[parse_pos] = NULLCHAR;
2266                         // try to be smart: if it does not look like search info, it should go to
2267                         // ICS interaction window after all, not to engine-output window.
2268                         for(i=0; i<parse_pos; i++) { // count letters and digits
2269                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2270                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2271                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2272                         }
2273                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2274                             int depth=0; float score;
2275                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2276                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2277                                 pvInfoList[forwardMostMove-1].depth = depth;
2278                                 pvInfoList[forwardMostMove-1].score = 100*score;
2279                             }
2280                             OutputKibitz(suppressKibitz, parse);
2281                         } else {
2282                             char tmp[MSG_SIZ];
2283                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2284                             SendToPlayer(tmp, strlen(tmp));
2285                         }
2286                     }
2287                     started = STARTED_NONE;
2288                 } else {
2289                     /* Don't match patterns against characters in chatter */
2290                     i++;
2291                     continue;
2292                 }
2293             }
2294             if (started == STARTED_CHATTER) {
2295                 if (buf[i] != '\n') {
2296                     /* Don't match patterns against characters in chatter */
2297                     i++;
2298                     continue;
2299                 }
2300                 started = STARTED_NONE;
2301             }
2302
2303             /* Kludge to deal with rcmd protocol */
2304             if (firstTime && looking_at(buf, &i, "\001*")) {
2305                 DisplayFatalError(&buf[1], 0, 1);
2306                 continue;
2307             } else {
2308                 firstTime = FALSE;
2309             }
2310
2311             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2312                 ics_type = ICS_ICC;
2313                 ics_prefix = "/";
2314                 if (appData.debugMode)
2315                   fprintf(debugFP, "ics_type %d\n", ics_type);
2316                 continue;
2317             }
2318             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2319                 ics_type = ICS_FICS;
2320                 ics_prefix = "$";
2321                 if (appData.debugMode)
2322                   fprintf(debugFP, "ics_type %d\n", ics_type);
2323                 continue;
2324             }
2325             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2326                 ics_type = ICS_CHESSNET;
2327                 ics_prefix = "/";
2328                 if (appData.debugMode)
2329                   fprintf(debugFP, "ics_type %d\n", ics_type);
2330                 continue;
2331             }
2332
2333             if (!loggedOn &&
2334                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2335                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2336                  looking_at(buf, &i, "will be \"*\""))) {
2337               strcpy(ics_handle, star_match[0]);
2338               continue;
2339             }
2340
2341             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2342               char buf[MSG_SIZ];
2343               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2344               DisplayIcsInteractionTitle(buf);
2345               have_set_title = TRUE;
2346             }
2347
2348             /* skip finger notes */
2349             if (started == STARTED_NONE &&
2350                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2351                  (buf[i] == '1' && buf[i+1] == '0')) &&
2352                 buf[i+2] == ':' && buf[i+3] == ' ') {
2353               started = STARTED_CHATTER;
2354               i += 3;
2355               continue;
2356             }
2357
2358             /* skip formula vars */
2359             if (started == STARTED_NONE &&
2360                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2361               started = STARTED_CHATTER;
2362               i += 3;
2363               continue;
2364             }
2365
2366             oldi = i;
2367             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2368             if (appData.autoKibitz && started == STARTED_NONE && 
2369                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2370                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2371                 if(looking_at(buf, &i, "* kibitzes: ") &&
2372                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2373                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2374                         suppressKibitz = TRUE;
2375                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2376                                 && (gameMode == IcsPlayingWhite)) ||
2377                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2378                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2379                             started = STARTED_CHATTER; // own kibitz we simply discard
2380                         else {
2381                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2382                             parse_pos = 0; parse[0] = NULLCHAR;
2383                             savingComment = TRUE;
2384                             suppressKibitz = gameMode != IcsObserving ? 2 :
2385                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2386                         } 
2387                         continue;
2388                 } else
2389                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2390                     started = STARTED_CHATTER;
2391                     suppressKibitz = TRUE;
2392                 }
2393             } // [HGM] kibitz: end of patch
2394
2395 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2396
2397             // [HGM] chat: intercept tells by users for which we have an open chat window
2398             channel = -1;
2399             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2400                                            looking_at(buf, &i, "* whispers:") ||
2401                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2402                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2403                 int p;
2404                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2405                 chattingPartner = -1;
2406
2407                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2408                 for(p=0; p<MAX_CHAT; p++) {
2409                     if(channel == atoi(chatPartner[p])) {
2410                     talker[0] = '['; strcat(talker, "]");
2411                     chattingPartner = p; break;
2412                     }
2413                 } else
2414                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2415                 for(p=0; p<MAX_CHAT; p++) {
2416                     if(!strcmp("WHISPER", chatPartner[p])) {
2417                         talker[0] = '['; strcat(talker, "]");
2418                         chattingPartner = p; break;
2419                     }
2420                 }
2421                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2422                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2423                     talker[0] = 0;
2424                     chattingPartner = p; break;
2425                 }
2426                 if(chattingPartner<0) i = oldi; else {
2427                     started = STARTED_COMMENT;
2428                     parse_pos = 0; parse[0] = NULLCHAR;
2429                     savingComment = TRUE;
2430                     suppressKibitz = TRUE;
2431                 }
2432             } // [HGM] chat: end of patch
2433
2434             if (appData.zippyTalk || appData.zippyPlay) {
2435                 /* [DM] Backup address for color zippy lines */
2436                 backup = i;
2437 #if ZIPPY
2438        #ifdef WIN32
2439                if (loggedOn == TRUE)
2440                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2441                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2442        #else
2443                 if (ZippyControl(buf, &i) ||
2444                     ZippyConverse(buf, &i) ||
2445                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2446                       loggedOn = TRUE;
2447                       if (!appData.colorize) continue;
2448                 }
2449        #endif
2450 #endif
2451             } // [DM] 'else { ' deleted
2452                 if (
2453                     /* Regular tells and says */
2454                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2455                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2456                     looking_at(buf, &i, "* says: ") ||
2457                     /* Don't color "message" or "messages" output */
2458                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2459                     looking_at(buf, &i, "*. * at *:*: ") ||
2460                     looking_at(buf, &i, "--* (*:*): ") ||
2461                     /* Message notifications (same color as tells) */
2462                     looking_at(buf, &i, "* has left a message ") ||
2463                     looking_at(buf, &i, "* just sent you a message:\n") ||
2464                     /* Whispers and kibitzes */
2465                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2466                     looking_at(buf, &i, "* kibitzes: ") ||
2467                     /* Channel tells */
2468                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2469
2470                   if (tkind == 1 && strchr(star_match[0], ':')) {
2471                       /* Avoid "tells you:" spoofs in channels */
2472                      tkind = 3;
2473                   }
2474                   if (star_match[0][0] == NULLCHAR ||
2475                       strchr(star_match[0], ' ') ||
2476                       (tkind == 3 && strchr(star_match[1], ' '))) {
2477                     /* Reject bogus matches */
2478                     i = oldi;
2479                   } else {
2480                     if (appData.colorize) {
2481                       if (oldi > next_out) {
2482                         SendToPlayer(&buf[next_out], oldi - next_out);
2483                         next_out = oldi;
2484                       }
2485                       switch (tkind) {
2486                       case 1:
2487                         Colorize(ColorTell, FALSE);
2488                         curColor = ColorTell;
2489                         break;
2490                       case 2:
2491                         Colorize(ColorKibitz, FALSE);
2492                         curColor = ColorKibitz;
2493                         break;
2494                       case 3:
2495                         p = strrchr(star_match[1], '(');
2496                         if (p == NULL) {
2497                           p = star_match[1];
2498                         } else {
2499                           p++;
2500                         }
2501                         if (atoi(p) == 1) {
2502                           Colorize(ColorChannel1, FALSE);
2503                           curColor = ColorChannel1;
2504                         } else {
2505                           Colorize(ColorChannel, FALSE);
2506                           curColor = ColorChannel;
2507                         }
2508                         break;
2509                       case 5:
2510                         curColor = ColorNormal;
2511                         break;
2512                       }
2513                     }
2514                     if (started == STARTED_NONE && appData.autoComment &&
2515                         (gameMode == IcsObserving ||
2516                          gameMode == IcsPlayingWhite ||
2517                          gameMode == IcsPlayingBlack)) {
2518                       parse_pos = i - oldi;
2519                       memcpy(parse, &buf[oldi], parse_pos);
2520                       parse[parse_pos] = NULLCHAR;
2521                       started = STARTED_COMMENT;
2522                       savingComment = TRUE;
2523                     } else {
2524                       started = STARTED_CHATTER;
2525                       savingComment = FALSE;
2526                     }
2527                     loggedOn = TRUE;
2528                     continue;
2529                   }
2530                 }
2531
2532                 if (looking_at(buf, &i, "* s-shouts: ") ||
2533                     looking_at(buf, &i, "* c-shouts: ")) {
2534                     if (appData.colorize) {
2535                         if (oldi > next_out) {
2536                             SendToPlayer(&buf[next_out], oldi - next_out);
2537                             next_out = oldi;
2538                         }
2539                         Colorize(ColorSShout, FALSE);
2540                         curColor = ColorSShout;
2541                     }
2542                     loggedOn = TRUE;
2543                     started = STARTED_CHATTER;
2544                     continue;
2545                 }
2546
2547                 if (looking_at(buf, &i, "--->")) {
2548                     loggedOn = TRUE;
2549                     continue;
2550                 }
2551
2552                 if (looking_at(buf, &i, "* shouts: ") ||
2553                     looking_at(buf, &i, "--> ")) {
2554                     if (appData.colorize) {
2555                         if (oldi > next_out) {
2556                             SendToPlayer(&buf[next_out], oldi - next_out);
2557                             next_out = oldi;
2558                         }
2559                         Colorize(ColorShout, FALSE);
2560                         curColor = ColorShout;
2561                     }
2562                     loggedOn = TRUE;
2563                     started = STARTED_CHATTER;
2564                     continue;
2565                 }
2566
2567                 if (looking_at( buf, &i, "Challenge:")) {
2568                     if (appData.colorize) {
2569                         if (oldi > next_out) {
2570                             SendToPlayer(&buf[next_out], oldi - next_out);
2571                             next_out = oldi;
2572                         }
2573                         Colorize(ColorChallenge, FALSE);
2574                         curColor = ColorChallenge;
2575                     }
2576                     loggedOn = TRUE;
2577                     continue;
2578                 }
2579
2580                 if (looking_at(buf, &i, "* offers you") ||
2581                     looking_at(buf, &i, "* offers to be") ||
2582                     looking_at(buf, &i, "* would like to") ||
2583                     looking_at(buf, &i, "* requests to") ||
2584                     looking_at(buf, &i, "Your opponent offers") ||
2585                     looking_at(buf, &i, "Your opponent requests")) {
2586
2587                     if (appData.colorize) {
2588                         if (oldi > next_out) {
2589                             SendToPlayer(&buf[next_out], oldi - next_out);
2590                             next_out = oldi;
2591                         }
2592                         Colorize(ColorRequest, FALSE);
2593                         curColor = ColorRequest;
2594                     }
2595                     continue;
2596                 }
2597
2598                 if (looking_at(buf, &i, "* (*) seeking")) {
2599                     if (appData.colorize) {
2600                         if (oldi > next_out) {
2601                             SendToPlayer(&buf[next_out], oldi - next_out);
2602                             next_out = oldi;
2603                         }
2604                         Colorize(ColorSeek, FALSE);
2605                         curColor = ColorSeek;
2606                     }
2607                     continue;
2608             }
2609
2610             if (looking_at(buf, &i, "\\   ")) {
2611                 if (prevColor != ColorNormal) {
2612                     if (oldi > next_out) {
2613                         SendToPlayer(&buf[next_out], oldi - next_out);
2614                         next_out = oldi;
2615                     }
2616                     Colorize(prevColor, TRUE);
2617                     curColor = prevColor;
2618                 }
2619                 if (savingComment) {
2620                     parse_pos = i - oldi;
2621                     memcpy(parse, &buf[oldi], parse_pos);
2622                     parse[parse_pos] = NULLCHAR;
2623                     started = STARTED_COMMENT;
2624                 } else {
2625                     started = STARTED_CHATTER;
2626                 }
2627                 continue;
2628             }
2629
2630             if (looking_at(buf, &i, "Black Strength :") ||
2631                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2632                 looking_at(buf, &i, "<10>") ||
2633                 looking_at(buf, &i, "#@#")) {
2634                 /* Wrong board style */
2635                 loggedOn = TRUE;
2636                 SendToICS(ics_prefix);
2637                 SendToICS("set style 12\n");
2638                 SendToICS(ics_prefix);
2639                 SendToICS("refresh\n");
2640                 continue;
2641             }
2642             
2643             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2644                 ICSInitScript();
2645                 have_sent_ICS_logon = 1;
2646                 continue;
2647             }
2648               
2649             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2650                 (looking_at(buf, &i, "\n<12> ") ||
2651                  looking_at(buf, &i, "<12> "))) {
2652                 loggedOn = TRUE;
2653                 if (oldi > next_out) {
2654                     SendToPlayer(&buf[next_out], oldi - next_out);
2655                 }
2656                 next_out = i;
2657                 started = STARTED_BOARD;
2658                 parse_pos = 0;
2659                 continue;
2660             }
2661
2662             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2663                 looking_at(buf, &i, "<b1> ")) {
2664                 if (oldi > next_out) {
2665                     SendToPlayer(&buf[next_out], oldi - next_out);
2666                 }
2667                 next_out = i;
2668                 started = STARTED_HOLDINGS;
2669                 parse_pos = 0;
2670                 continue;
2671             }
2672
2673             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2674                 loggedOn = TRUE;
2675                 /* Header for a move list -- first line */
2676
2677                 switch (ics_getting_history) {
2678                   case H_FALSE:
2679                     switch (gameMode) {
2680                       case IcsIdle:
2681                       case BeginningOfGame:
2682                         /* User typed "moves" or "oldmoves" while we
2683                            were idle.  Pretend we asked for these
2684                            moves and soak them up so user can step
2685                            through them and/or save them.
2686                            */
2687                         Reset(FALSE, TRUE);
2688                         gameMode = IcsObserving;
2689                         ModeHighlight();
2690                         ics_gamenum = -1;
2691                         ics_getting_history = H_GOT_UNREQ_HEADER;
2692                         break;
2693                       case EditGame: /*?*/
2694                       case EditPosition: /*?*/
2695                         /* Should above feature work in these modes too? */
2696                         /* For now it doesn't */
2697                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2698                         break;
2699                       default:
2700                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2701                         break;
2702                     }
2703                     break;
2704                   case H_REQUESTED:
2705                     /* Is this the right one? */
2706                     if (gameInfo.white && gameInfo.black &&
2707                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2708                         strcmp(gameInfo.black, star_match[2]) == 0) {
2709                         /* All is well */
2710                         ics_getting_history = H_GOT_REQ_HEADER;
2711                     }
2712                     break;
2713                   case H_GOT_REQ_HEADER:
2714                   case H_GOT_UNREQ_HEADER:
2715                   case H_GOT_UNWANTED_HEADER:
2716                   case H_GETTING_MOVES:
2717                     /* Should not happen */
2718                     DisplayError(_("Error gathering move list: two headers"), 0);
2719                     ics_getting_history = H_FALSE;
2720                     break;
2721                 }
2722
2723                 /* Save player ratings into gameInfo if needed */
2724                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2725                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2726                     (gameInfo.whiteRating == -1 ||
2727                      gameInfo.blackRating == -1)) {
2728
2729                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2730                     gameInfo.blackRating = string_to_rating(star_match[3]);
2731                     if (appData.debugMode)
2732                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2733                               gameInfo.whiteRating, gameInfo.blackRating);
2734                 }
2735                 continue;
2736             }
2737
2738             if (looking_at(buf, &i,
2739               "* * match, initial time: * minute*, increment: * second")) {
2740                 /* Header for a move list -- second line */
2741                 /* Initial board will follow if this is a wild game */
2742                 if (gameInfo.event != NULL) free(gameInfo.event);
2743                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2744                 gameInfo.event = StrSave(str);
2745                 /* [HGM] we switched variant. Translate boards if needed. */
2746                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2747                 continue;
2748             }
2749
2750             if (looking_at(buf, &i, "Move  ")) {
2751                 /* Beginning of a move list */
2752                 switch (ics_getting_history) {
2753                   case H_FALSE:
2754                     /* Normally should not happen */
2755                     /* Maybe user hit reset while we were parsing */
2756                     break;
2757                   case H_REQUESTED:
2758                     /* Happens if we are ignoring a move list that is not
2759                      * the one we just requested.  Common if the user
2760                      * tries to observe two games without turning off
2761                      * getMoveList */
2762                     break;
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: nested"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                   case H_GOT_REQ_HEADER:
2769                     ics_getting_history = H_GETTING_MOVES;
2770                     started = STARTED_MOVES;
2771                     parse_pos = 0;
2772                     if (oldi > next_out) {
2773                         SendToPlayer(&buf[next_out], oldi - next_out);
2774                     }
2775                     break;
2776                   case H_GOT_UNREQ_HEADER:
2777                     ics_getting_history = H_GETTING_MOVES;
2778                     started = STARTED_MOVES_NOHIDE;
2779                     parse_pos = 0;
2780                     break;
2781                   case H_GOT_UNWANTED_HEADER:
2782                     ics_getting_history = H_FALSE;
2783                     break;
2784                 }
2785                 continue;
2786             }                           
2787             
2788             if (looking_at(buf, &i, "% ") ||
2789                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2790                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2791                 savingComment = FALSE;
2792                 switch (started) {
2793                   case STARTED_MOVES:
2794                   case STARTED_MOVES_NOHIDE:
2795                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2796                     parse[parse_pos + i - oldi] = NULLCHAR;
2797                     ParseGameHistory(parse);
2798 #if ZIPPY
2799                     if (appData.zippyPlay && first.initDone) {
2800                         FeedMovesToProgram(&first, forwardMostMove);
2801                         if (gameMode == IcsPlayingWhite) {
2802                             if (WhiteOnMove(forwardMostMove)) {
2803                                 if (first.sendTime) {
2804                                   if (first.useColors) {
2805                                     SendToProgram("black\n", &first); 
2806                                   }
2807                                   SendTimeRemaining(&first, TRUE);
2808                                 }
2809                                 if (first.useColors) {
2810                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2811                                 }
2812                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2813                                 first.maybeThinking = TRUE;
2814                             } else {
2815                                 if (first.usePlayother) {
2816                                   if (first.sendTime) {
2817                                     SendTimeRemaining(&first, TRUE);
2818                                   }
2819                                   SendToProgram("playother\n", &first);
2820                                   firstMove = FALSE;
2821                                 } else {
2822                                   firstMove = TRUE;
2823                                 }
2824                             }
2825                         } else if (gameMode == IcsPlayingBlack) {
2826                             if (!WhiteOnMove(forwardMostMove)) {
2827                                 if (first.sendTime) {
2828                                   if (first.useColors) {
2829                                     SendToProgram("white\n", &first);
2830                                   }
2831                                   SendTimeRemaining(&first, FALSE);
2832                                 }
2833                                 if (first.useColors) {
2834                                   SendToProgram("black\n", &first);
2835                                 }
2836                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2837                                 first.maybeThinking = TRUE;
2838                             } else {
2839                                 if (first.usePlayother) {
2840                                   if (first.sendTime) {
2841                                     SendTimeRemaining(&first, FALSE);
2842                                   }
2843                                   SendToProgram("playother\n", &first);
2844                                   firstMove = FALSE;
2845                                 } else {
2846                                   firstMove = TRUE;
2847                                 }
2848                             }
2849                         }                       
2850                     }
2851 #endif
2852                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2853                         /* Moves came from oldmoves or moves command
2854                            while we weren't doing anything else.
2855                            */
2856                         currentMove = forwardMostMove;
2857                         ClearHighlights();/*!!could figure this out*/
2858                         flipView = appData.flipView;
2859                         DrawPosition(FALSE, boards[currentMove]);
2860                         DisplayBothClocks();
2861                         sprintf(str, "%s vs. %s",
2862                                 gameInfo.white, gameInfo.black);
2863                         DisplayTitle(str);
2864                         gameMode = IcsIdle;
2865                     } else {
2866                         /* Moves were history of an active game */
2867                         if (gameInfo.resultDetails != NULL) {
2868                             free(gameInfo.resultDetails);
2869                             gameInfo.resultDetails = NULL;
2870                         }
2871                     }
2872                     HistorySet(parseList, backwardMostMove,
2873                                forwardMostMove, currentMove-1);
2874                     DisplayMove(currentMove - 1);
2875                     if (started == STARTED_MOVES) next_out = i;
2876                     started = STARTED_NONE;
2877                     ics_getting_history = H_FALSE;
2878                     break;
2879
2880                   case STARTED_OBSERVE:
2881                     started = STARTED_NONE;
2882                     SendToICS(ics_prefix);
2883                     SendToICS("refresh\n");
2884                     break;
2885
2886                   default:
2887                     break;
2888                 }
2889                 if(bookHit) { // [HGM] book: simulate book reply
2890                     static char bookMove[MSG_SIZ]; // a bit generous?
2891
2892                     programStats.nodes = programStats.depth = programStats.time = 
2893                     programStats.score = programStats.got_only_move = 0;
2894                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2895
2896                     strcpy(bookMove, "move ");
2897                     strcat(bookMove, bookHit);
2898                     HandleMachineMove(bookMove, &first);
2899                 }
2900                 continue;
2901             }
2902             
2903             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2904                  started == STARTED_HOLDINGS ||
2905                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2906                 /* Accumulate characters in move list or board */
2907                 parse[parse_pos++] = buf[i];
2908             }
2909             
2910             /* Start of game messages.  Mostly we detect start of game
2911                when the first board image arrives.  On some versions
2912                of the ICS, though, we need to do a "refresh" after starting
2913                to observe in order to get the current board right away. */
2914             if (looking_at(buf, &i, "Adding game * to observation list")) {
2915                 started = STARTED_OBSERVE;
2916                 continue;
2917             }
2918
2919             /* Handle auto-observe */
2920             if (appData.autoObserve &&
2921                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2922                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2923                 char *player;
2924                 /* Choose the player that was highlighted, if any. */
2925                 if (star_match[0][0] == '\033' ||
2926                     star_match[1][0] != '\033') {
2927                     player = star_match[0];
2928                 } else {
2929                     player = star_match[2];
2930                 }
2931                 sprintf(str, "%sobserve %s\n",
2932                         ics_prefix, StripHighlightAndTitle(player));
2933                 SendToICS(str);
2934
2935                 /* Save ratings from notify string */
2936                 strcpy(player1Name, star_match[0]);
2937                 player1Rating = string_to_rating(star_match[1]);
2938                 strcpy(player2Name, star_match[2]);
2939                 player2Rating = string_to_rating(star_match[3]);
2940
2941                 if (appData.debugMode)
2942                   fprintf(debugFP, 
2943                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2944                           player1Name, player1Rating,
2945                           player2Name, player2Rating);
2946
2947                 continue;
2948             }
2949
2950             /* Deal with automatic examine mode after a game,
2951                and with IcsObserving -> IcsExamining transition */
2952             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2953                 looking_at(buf, &i, "has made you an examiner of game *")) {
2954
2955                 int gamenum = atoi(star_match[0]);
2956                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2957                     gamenum == ics_gamenum) {
2958                     /* We were already playing or observing this game;
2959                        no need to refetch history */
2960                     gameMode = IcsExamining;
2961                     if (pausing) {
2962                         pauseExamForwardMostMove = forwardMostMove;
2963                     } else if (currentMove < forwardMostMove) {
2964                         ForwardInner(forwardMostMove);
2965                     }
2966                 } else {
2967                     /* I don't think this case really can happen */
2968                     SendToICS(ics_prefix);
2969                     SendToICS("refresh\n");
2970                 }
2971                 continue;
2972             }    
2973             
2974             /* Error messages */
2975 //          if (ics_user_moved) {
2976             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2977                 if (looking_at(buf, &i, "Illegal move") ||
2978                     looking_at(buf, &i, "Not a legal move") ||
2979                     looking_at(buf, &i, "Your king is in check") ||
2980                     looking_at(buf, &i, "It isn't your turn") ||
2981                     looking_at(buf, &i, "It is not your move")) {
2982                     /* Illegal move */
2983                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2984                         currentMove = --forwardMostMove;
2985                         DisplayMove(currentMove - 1); /* before DMError */
2986                         DrawPosition(FALSE, boards[currentMove]);
2987                         SwitchClocks();
2988                         DisplayBothClocks();
2989                     }
2990                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2991                     ics_user_moved = 0;
2992                     continue;
2993                 }
2994             }
2995
2996             if (looking_at(buf, &i, "still have time") ||
2997                 looking_at(buf, &i, "not out of time") ||
2998                 looking_at(buf, &i, "either player is out of time") ||
2999                 looking_at(buf, &i, "has timeseal; checking")) {
3000                 /* We must have called his flag a little too soon */
3001                 whiteFlag = blackFlag = FALSE;
3002                 continue;
3003             }
3004
3005             if (looking_at(buf, &i, "added * seconds to") ||
3006                 looking_at(buf, &i, "seconds were added to")) {
3007                 /* Update the clocks */
3008                 SendToICS(ics_prefix);
3009                 SendToICS("refresh\n");
3010                 continue;
3011             }
3012
3013             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3014                 ics_clock_paused = TRUE;
3015                 StopClocks();
3016                 continue;
3017             }
3018
3019             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3020                 ics_clock_paused = FALSE;
3021                 StartClocks();
3022                 continue;
3023             }
3024
3025             /* Grab player ratings from the Creating: message.
3026                Note we have to check for the special case when
3027                the ICS inserts things like [white] or [black]. */
3028             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3029                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3030                 /* star_matches:
3031                    0    player 1 name (not necessarily white)
3032                    1    player 1 rating
3033                    2    empty, white, or black (IGNORED)
3034                    3    player 2 name (not necessarily black)
3035                    4    player 2 rating
3036                    
3037                    The names/ratings are sorted out when the game
3038                    actually starts (below).
3039                 */
3040                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3041                 player1Rating = string_to_rating(star_match[1]);
3042                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3043                 player2Rating = string_to_rating(star_match[4]);
3044
3045                 if (appData.debugMode)
3046                   fprintf(debugFP, 
3047                           "Ratings from 'Creating:' %s %d, %s %d\n",
3048                           player1Name, player1Rating,
3049                           player2Name, player2Rating);
3050
3051                 continue;
3052             }
3053             
3054             /* Improved generic start/end-of-game messages */
3055             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3056                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3057                 /* If tkind == 0: */
3058                 /* star_match[0] is the game number */
3059                 /*           [1] is the white player's name */
3060                 /*           [2] is the black player's name */
3061                 /* For end-of-game: */
3062                 /*           [3] is the reason for the game end */
3063                 /*           [4] is a PGN end game-token, preceded by " " */
3064                 /* For start-of-game: */
3065                 /*           [3] begins with "Creating" or "Continuing" */
3066                 /*           [4] is " *" or empty (don't care). */
3067                 int gamenum = atoi(star_match[0]);
3068                 char *whitename, *blackname, *why, *endtoken;
3069                 ChessMove endtype = (ChessMove) 0;
3070
3071                 if (tkind == 0) {
3072                   whitename = star_match[1];
3073                   blackname = star_match[2];
3074                   why = star_match[3];
3075                   endtoken = star_match[4];
3076                 } else {
3077                   whitename = star_match[1];
3078                   blackname = star_match[3];
3079                   why = star_match[5];
3080                   endtoken = star_match[6];
3081                 }
3082
3083                 /* Game start messages */
3084                 if (strncmp(why, "Creating ", 9) == 0 ||
3085                     strncmp(why, "Continuing ", 11) == 0) {
3086                     gs_gamenum = gamenum;
3087                     strcpy(gs_kind, strchr(why, ' ') + 1);
3088 #if ZIPPY
3089                     if (appData.zippyPlay) {
3090                         ZippyGameStart(whitename, blackname);
3091                     }
3092 #endif /*ZIPPY*/
3093                     continue;
3094                 }
3095
3096                 /* Game end messages */
3097                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3098                     ics_gamenum != gamenum) {
3099                     continue;
3100                 }
3101                 while (endtoken[0] == ' ') endtoken++;
3102                 switch (endtoken[0]) {
3103                   case '*':
3104                   default:
3105                     endtype = GameUnfinished;
3106                     break;
3107                   case '0':
3108                     endtype = BlackWins;
3109                     break;
3110                   case '1':
3111                     if (endtoken[1] == '/')
3112                       endtype = GameIsDrawn;
3113                     else
3114                       endtype = WhiteWins;
3115                     break;
3116                 }
3117                 GameEnds(endtype, why, GE_ICS);
3118 #if ZIPPY
3119                 if (appData.zippyPlay && first.initDone) {
3120                     ZippyGameEnd(endtype, why);
3121                     if (first.pr == NULL) {
3122                       /* Start the next process early so that we'll
3123                          be ready for the next challenge */
3124                       StartChessProgram(&first);
3125                     }
3126                     /* Send "new" early, in case this command takes
3127                        a long time to finish, so that we'll be ready
3128                        for the next challenge. */
3129                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3130                     Reset(TRUE, TRUE);
3131                 }
3132 #endif /*ZIPPY*/
3133                 continue;
3134             }
3135
3136             if (looking_at(buf, &i, "Removing game * from observation") ||
3137                 looking_at(buf, &i, "no longer observing game *") ||
3138                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3139                 if (gameMode == IcsObserving &&
3140                     atoi(star_match[0]) == ics_gamenum)
3141                   {
3142                       /* icsEngineAnalyze */
3143                       if (appData.icsEngineAnalyze) {
3144                             ExitAnalyzeMode();
3145                             ModeHighlight();
3146                       }
3147                       StopClocks();
3148                       gameMode = IcsIdle;
3149                       ics_gamenum = -1;
3150                       ics_user_moved = FALSE;
3151                   }
3152                 continue;
3153             }
3154
3155             if (looking_at(buf, &i, "no longer examining game *")) {
3156                 if (gameMode == IcsExamining &&
3157                     atoi(star_match[0]) == ics_gamenum)
3158                   {
3159                       gameMode = IcsIdle;
3160                       ics_gamenum = -1;
3161                       ics_user_moved = FALSE;
3162                   }
3163                 continue;
3164             }
3165
3166             /* Advance leftover_start past any newlines we find,
3167                so only partial lines can get reparsed */
3168             if (looking_at(buf, &i, "\n")) {
3169                 prevColor = curColor;
3170                 if (curColor != ColorNormal) {
3171                     if (oldi > next_out) {
3172                         SendToPlayer(&buf[next_out], oldi - next_out);
3173                         next_out = oldi;
3174                     }
3175                     Colorize(ColorNormal, FALSE);
3176                     curColor = ColorNormal;
3177                 }
3178                 if (started == STARTED_BOARD) {
3179                     started = STARTED_NONE;
3180                     parse[parse_pos] = NULLCHAR;
3181                     ParseBoard12(parse);
3182                     ics_user_moved = 0;
3183
3184                     /* Send premove here */
3185                     if (appData.premove) {
3186                       char str[MSG_SIZ];
3187                       if (currentMove == 0 &&
3188                           gameMode == IcsPlayingWhite &&
3189                           appData.premoveWhite) {
3190                         sprintf(str, "%s%s\n", ics_prefix,
3191                                 appData.premoveWhiteText);
3192                         if (appData.debugMode)
3193                           fprintf(debugFP, "Sending premove:\n");
3194                         SendToICS(str);
3195                       } else if (currentMove == 1 &&
3196                                  gameMode == IcsPlayingBlack &&
3197                                  appData.premoveBlack) {
3198                         sprintf(str, "%s%s\n", ics_prefix,
3199                                 appData.premoveBlackText);
3200                         if (appData.debugMode)
3201                           fprintf(debugFP, "Sending premove:\n");
3202                         SendToICS(str);
3203                       } else if (gotPremove) {
3204                         gotPremove = 0;
3205                         ClearPremoveHighlights();
3206                         if (appData.debugMode)
3207                           fprintf(debugFP, "Sending premove:\n");
3208                           UserMoveEvent(premoveFromX, premoveFromY, 
3209                                         premoveToX, premoveToY, 
3210                                         premovePromoChar);
3211                       }
3212                     }
3213
3214                     /* Usually suppress following prompt */
3215                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3216                         if (looking_at(buf, &i, "*% ")) {
3217                             savingComment = FALSE;
3218                         }
3219                     }
3220                     next_out = i;
3221                 } else if (started == STARTED_HOLDINGS) {
3222                     int gamenum;
3223                     char new_piece[MSG_SIZ];
3224                     started = STARTED_NONE;
3225                     parse[parse_pos] = NULLCHAR;
3226                     if (appData.debugMode)
3227                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3228                                                         parse, currentMove);
3229                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3230                         gamenum == ics_gamenum) {
3231                         if (gameInfo.variant == VariantNormal) {
3232                           /* [HGM] We seem to switch variant during a game!
3233                            * Presumably no holdings were displayed, so we have
3234                            * to move the position two files to the right to
3235                            * create room for them!
3236                            */
3237                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3238                           /* Get a move list just to see the header, which
3239                              will tell us whether this is really bug or zh */
3240                           if (ics_getting_history == H_FALSE) {
3241                             ics_getting_history = H_REQUESTED;
3242                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3243                             SendToICS(str);
3244                           }
3245                         }
3246                         new_piece[0] = NULLCHAR;
3247                         sscanf(parse, "game %d white [%s black [%s <- %s",
3248                                &gamenum, white_holding, black_holding,
3249                                new_piece);
3250                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3251                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3252                         /* [HGM] copy holdings to board holdings area */
3253                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3254                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3255 #if ZIPPY
3256                         if (appData.zippyPlay && first.initDone) {
3257                             ZippyHoldings(white_holding, black_holding,
3258                                           new_piece);
3259                         }
3260 #endif /*ZIPPY*/
3261                         if (tinyLayout || smallLayout) {
3262                             char wh[16], bh[16];
3263                             PackHolding(wh, white_holding);
3264                             PackHolding(bh, black_holding);
3265                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3266                                     gameInfo.white, gameInfo.black);
3267                         } else {
3268                             sprintf(str, "%s [%s] vs. %s [%s]",
3269                                     gameInfo.white, white_holding,
3270                                     gameInfo.black, black_holding);
3271                         }
3272
3273                         DrawPosition(FALSE, boards[currentMove]);
3274                         DisplayTitle(str);
3275                     }
3276                     /* Suppress following prompt */
3277                     if (looking_at(buf, &i, "*% ")) {
3278                         savingComment = FALSE;
3279                     }
3280                     next_out = i;
3281                 }
3282                 continue;
3283             }
3284
3285             i++;                /* skip unparsed character and loop back */
3286         }
3287         
3288         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3289             started != STARTED_HOLDINGS && i > next_out) {
3290             SendToPlayer(&buf[next_out], i - next_out);
3291             next_out = i;
3292         }
3293         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3294         
3295         leftover_len = buf_len - leftover_start;
3296         /* if buffer ends with something we couldn't parse,
3297            reparse it after appending the next read */
3298         
3299     } else if (count == 0) {
3300         RemoveInputSource(isr);
3301         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3302     } else {
3303         DisplayFatalError(_("Error reading from ICS"), error, 1);
3304     }
3305 }
3306
3307
3308 /* Board style 12 looks like this:
3309    
3310    <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
3311    
3312  * The "<12> " is stripped before it gets to this routine.  The two
3313  * trailing 0's (flip state and clock ticking) are later addition, and
3314  * some chess servers may not have them, or may have only the first.
3315  * Additional trailing fields may be added in the future.  
3316  */
3317
3318 #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"
3319
3320 #define RELATION_OBSERVING_PLAYED    0
3321 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3322 #define RELATION_PLAYING_MYMOVE      1
3323 #define RELATION_PLAYING_NOTMYMOVE  -1
3324 #define RELATION_EXAMINING           2
3325 #define RELATION_ISOLATED_BOARD     -3
3326 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3327
3328 void
3329 ParseBoard12(string)
3330      char *string;
3331
3332     GameMode newGameMode;
3333     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3334     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3335     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3336     char to_play, board_chars[200];
3337     char move_str[500], str[500], elapsed_time[500];
3338     char black[32], white[32];
3339     Board board;
3340     int prevMove = currentMove;
3341     int ticking = 2;
3342     ChessMove moveType;
3343     int fromX, fromY, toX, toY;
3344     char promoChar;
3345     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3346     char *bookHit = NULL; // [HGM] book
3347
3348     fromX = fromY = toX = toY = -1;
3349     
3350     newGame = FALSE;
3351
3352     if (appData.debugMode)
3353       fprintf(debugFP, _("Parsing board: %s\n"), string);
3354
3355     move_str[0] = NULLCHAR;
3356     elapsed_time[0] = NULLCHAR;
3357     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3358         int  i = 0, j;
3359         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3360             if(string[i] == ' ') { ranks++; files = 0; }
3361             else files++;
3362             i++;
3363         }
3364         for(j = 0; j <i; j++) board_chars[j] = string[j];
3365         board_chars[i] = '\0';
3366         string += i + 1;
3367     }
3368     n = sscanf(string, PATTERN, &to_play, &double_push,
3369                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3370                &gamenum, white, black, &relation, &basetime, &increment,
3371                &white_stren, &black_stren, &white_time, &black_time,
3372                &moveNum, str, elapsed_time, move_str, &ics_flip,
3373                &ticking);
3374
3375     if (n < 21) {
3376         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3377         DisplayError(str, 0);
3378         return;
3379     }
3380
3381     /* Convert the move number to internal form */
3382     moveNum = (moveNum - 1) * 2;
3383     if (to_play == 'B') moveNum++;
3384     if (moveNum >= MAX_MOVES) {
3385       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3386                         0, 1);
3387       return;
3388     }
3389     
3390     switch (relation) {
3391       case RELATION_OBSERVING_PLAYED:
3392       case RELATION_OBSERVING_STATIC:
3393         if (gamenum == -1) {
3394             /* Old ICC buglet */
3395             relation = RELATION_OBSERVING_STATIC;
3396         }
3397         newGameMode = IcsObserving;
3398         break;
3399       case RELATION_PLAYING_MYMOVE:
3400       case RELATION_PLAYING_NOTMYMOVE:
3401         newGameMode =
3402           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3403             IcsPlayingWhite : IcsPlayingBlack;
3404         break;
3405       case RELATION_EXAMINING:
3406         newGameMode = IcsExamining;
3407         break;
3408       case RELATION_ISOLATED_BOARD:
3409       default:
3410         /* Just display this board.  If user was doing something else,
3411            we will forget about it until the next board comes. */ 
3412         newGameMode = IcsIdle;
3413         break;
3414       case RELATION_STARTING_POSITION:
3415         newGameMode = gameMode;
3416         break;
3417     }
3418     
3419     /* Modify behavior for initial board display on move listing
3420        of wild games.
3421        */
3422     switch (ics_getting_history) {
3423       case H_FALSE:
3424       case H_REQUESTED:
3425         break;
3426       case H_GOT_REQ_HEADER:
3427       case H_GOT_UNREQ_HEADER:
3428         /* This is the initial position of the current game */
3429         gamenum = ics_gamenum;
3430         moveNum = 0;            /* old ICS bug workaround */
3431         if (to_play == 'B') {
3432           startedFromSetupPosition = TRUE;
3433           blackPlaysFirst = TRUE;
3434           moveNum = 1;
3435           if (forwardMostMove == 0) forwardMostMove = 1;
3436           if (backwardMostMove == 0) backwardMostMove = 1;
3437           if (currentMove == 0) currentMove = 1;
3438         }
3439         newGameMode = gameMode;
3440         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3441         break;
3442       case H_GOT_UNWANTED_HEADER:
3443         /* This is an initial board that we don't want */
3444         return;
3445       case H_GETTING_MOVES:
3446         /* Should not happen */
3447         DisplayError(_("Error gathering move list: extra board"), 0);
3448         ics_getting_history = H_FALSE;
3449         return;
3450     }
3451     
3452     /* Take action if this is the first board of a new game, or of a
3453        different game than is currently being displayed.  */
3454     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3455         relation == RELATION_ISOLATED_BOARD) {
3456         
3457         /* Forget the old game and get the history (if any) of the new one */
3458         if (gameMode != BeginningOfGame) {
3459           Reset(FALSE, TRUE);
3460         }
3461         newGame = TRUE;
3462         if (appData.autoRaiseBoard) BoardToTop();
3463         prevMove = -3;
3464         if (gamenum == -1) {
3465             newGameMode = IcsIdle;
3466         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3467                    appData.getMoveList) {
3468             /* Need to get game history */
3469             ics_getting_history = H_REQUESTED;
3470             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3471             SendToICS(str);
3472         }
3473         
3474         /* Initially flip the board to have black on the bottom if playing
3475            black or if the ICS flip flag is set, but let the user change
3476            it with the Flip View button. */
3477         flipView = appData.autoFlipView ? 
3478           (newGameMode == IcsPlayingBlack) || ics_flip :
3479           appData.flipView;
3480         
3481         /* Done with values from previous mode; copy in new ones */
3482         gameMode = newGameMode;
3483         ModeHighlight();
3484         ics_gamenum = gamenum;
3485         if (gamenum == gs_gamenum) {
3486             int klen = strlen(gs_kind);
3487             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3488             sprintf(str, "ICS %s", gs_kind);
3489             gameInfo.event = StrSave(str);
3490         } else {
3491             gameInfo.event = StrSave("ICS game");
3492         }
3493         gameInfo.site = StrSave(appData.icsHost);
3494         gameInfo.date = PGNDate();
3495         gameInfo.round = StrSave("-");
3496         gameInfo.white = StrSave(white);
3497         gameInfo.black = StrSave(black);
3498         timeControl = basetime * 60 * 1000;
3499         timeControl_2 = 0;
3500         timeIncrement = increment * 1000;
3501         movesPerSession = 0;
3502         gameInfo.timeControl = TimeControlTagValue();
3503         VariantSwitch(board, StringToVariant(gameInfo.event) );
3504   if (appData.debugMode) {
3505     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3506     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3507     setbuf(debugFP, NULL);
3508   }
3509
3510         gameInfo.outOfBook = NULL;
3511         
3512         /* Do we have the ratings? */
3513         if (strcmp(player1Name, white) == 0 &&
3514             strcmp(player2Name, black) == 0) {
3515             if (appData.debugMode)
3516               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3517                       player1Rating, player2Rating);
3518             gameInfo.whiteRating = player1Rating;
3519             gameInfo.blackRating = player2Rating;
3520         } else if (strcmp(player2Name, white) == 0 &&
3521                    strcmp(player1Name, black) == 0) {
3522             if (appData.debugMode)
3523               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3524                       player2Rating, player1Rating);
3525             gameInfo.whiteRating = player2Rating;
3526             gameInfo.blackRating = player1Rating;
3527         }
3528         player1Name[0] = player2Name[0] = NULLCHAR;
3529
3530         /* Silence shouts if requested */
3531         if (appData.quietPlay &&
3532             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3533             SendToICS(ics_prefix);
3534             SendToICS("set shout 0\n");
3535         }
3536     }
3537     
3538     /* Deal with midgame name changes */
3539     if (!newGame) {
3540         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3541             if (gameInfo.white) free(gameInfo.white);
3542             gameInfo.white = StrSave(white);
3543         }
3544         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3545             if (gameInfo.black) free(gameInfo.black);
3546             gameInfo.black = StrSave(black);
3547         }
3548     }
3549     
3550     /* Throw away game result if anything actually changes in examine mode */
3551     if (gameMode == IcsExamining && !newGame) {
3552         gameInfo.result = GameUnfinished;
3553         if (gameInfo.resultDetails != NULL) {
3554             free(gameInfo.resultDetails);
3555             gameInfo.resultDetails = NULL;
3556         }
3557     }
3558     
3559     /* In pausing && IcsExamining mode, we ignore boards coming
3560        in if they are in a different variation than we are. */
3561     if (pauseExamInvalid) return;
3562     if (pausing && gameMode == IcsExamining) {
3563         if (moveNum <= pauseExamForwardMostMove) {
3564             pauseExamInvalid = TRUE;
3565             forwardMostMove = pauseExamForwardMostMove;
3566             return;
3567         }
3568     }
3569     
3570   if (appData.debugMode) {
3571     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3572   }
3573     /* Parse the board */
3574     for (k = 0; k < ranks; k++) {
3575       for (j = 0; j < files; j++)
3576         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3577       if(gameInfo.holdingsWidth > 1) {
3578            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3579            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3580       }
3581     }
3582     CopyBoard(boards[moveNum], board);
3583     if (moveNum == 0) {
3584         startedFromSetupPosition =
3585           !CompareBoards(board, initialPosition);
3586         if(startedFromSetupPosition)
3587             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3588     }
3589
3590     /* [HGM] Set castling rights. Take the outermost Rooks,
3591        to make it also work for FRC opening positions. Note that board12
3592        is really defective for later FRC positions, as it has no way to
3593        indicate which Rook can castle if they are on the same side of King.
3594        For the initial position we grant rights to the outermost Rooks,
3595        and remember thos rights, and we then copy them on positions
3596        later in an FRC game. This means WB might not recognize castlings with
3597        Rooks that have moved back to their original position as illegal,
3598        but in ICS mode that is not its job anyway.
3599     */
3600     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3601     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3602
3603         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3604             if(board[0][i] == WhiteRook) j = i;
3605         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3606         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3607             if(board[0][i] == WhiteRook) j = i;
3608         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3609         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3610             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3611         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3612         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3613             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3614         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3615
3616         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3617         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3618             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3619         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3620             if(board[BOARD_HEIGHT-1][k] == bKing)
3621                 initialRights[5] = castlingRights[moveNum][5] = k;
3622     } else { int r;
3623         r = castlingRights[moveNum][0] = initialRights[0];
3624         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3625         r = castlingRights[moveNum][1] = initialRights[1];
3626         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3627         r = castlingRights[moveNum][3] = initialRights[3];
3628         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3629         r = castlingRights[moveNum][4] = initialRights[4];
3630         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3631         /* wildcastle kludge: always assume King has rights */
3632         r = castlingRights[moveNum][2] = initialRights[2];
3633         r = castlingRights[moveNum][5] = initialRights[5];
3634     }
3635     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3636     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3637
3638     
3639     if (ics_getting_history == H_GOT_REQ_HEADER ||
3640         ics_getting_history == H_GOT_UNREQ_HEADER) {
3641         /* This was an initial position from a move list, not
3642            the current position */
3643         return;
3644     }
3645     
3646     /* Update currentMove and known move number limits */
3647     newMove = newGame || moveNum > forwardMostMove;
3648
3649     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3650     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3651         takeback = forwardMostMove - moveNum;
3652         for (i = 0; i < takeback; i++) {
3653              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3654              SendToProgram("undo\n", &first);
3655         }
3656     }
3657
3658     if (newGame) {
3659         forwardMostMove = backwardMostMove = currentMove = moveNum;
3660         if (gameMode == IcsExamining && moveNum == 0) {
3661           /* Workaround for ICS limitation: we are not told the wild
3662              type when starting to examine a game.  But if we ask for
3663              the move list, the move list header will tell us */
3664             ics_getting_history = H_REQUESTED;
3665             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3666             SendToICS(str);
3667         }
3668     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3669                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3670         forwardMostMove = moveNum;
3671         if (!pausing || currentMove > forwardMostMove)
3672           currentMove = forwardMostMove;
3673     } else {
3674         /* New part of history that is not contiguous with old part */ 
3675         if (pausing && gameMode == IcsExamining) {
3676             pauseExamInvalid = TRUE;
3677             forwardMostMove = pauseExamForwardMostMove;
3678             return;
3679         }
3680         forwardMostMove = backwardMostMove = currentMove = moveNum;
3681         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3682             ics_getting_history = H_REQUESTED;
3683             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3684             SendToICS(str);
3685         }
3686     }
3687     
3688     /* Update the clocks */
3689     if (strchr(elapsed_time, '.')) {
3690       /* Time is in ms */
3691       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3692       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3693     } else {
3694       /* Time is in seconds */
3695       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3696       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3697     }
3698       
3699
3700 #if ZIPPY
3701     if (appData.zippyPlay && newGame &&
3702         gameMode != IcsObserving && gameMode != IcsIdle &&
3703         gameMode != IcsExamining)
3704       ZippyFirstBoard(moveNum, basetime, increment);
3705 #endif
3706     
3707     /* Put the move on the move list, first converting
3708        to canonical algebraic form. */
3709     if (moveNum > 0) {
3710   if (appData.debugMode) {
3711     if (appData.debugMode) { int f = forwardMostMove;
3712         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3713                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3714     }
3715     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3716     fprintf(debugFP, "moveNum = %d\n", moveNum);
3717     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3718     setbuf(debugFP, NULL);
3719   }
3720         if (moveNum <= backwardMostMove) {
3721             /* We don't know what the board looked like before
3722                this move.  Punt. */
3723             strcpy(parseList[moveNum - 1], move_str);
3724             strcat(parseList[moveNum - 1], " ");
3725             strcat(parseList[moveNum - 1], elapsed_time);
3726             moveList[moveNum - 1][0] = NULLCHAR;
3727         } else if (strcmp(move_str, "none") == 0) {
3728             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3729             /* Again, we don't know what the board looked like;
3730                this is really the start of the game. */
3731             parseList[moveNum - 1][0] = NULLCHAR;
3732             moveList[moveNum - 1][0] = NULLCHAR;
3733             backwardMostMove = moveNum;
3734             startedFromSetupPosition = TRUE;
3735             fromX = fromY = toX = toY = -1;
3736         } else {
3737           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3738           //                 So we parse the long-algebraic move string in stead of the SAN move
3739           int valid; char buf[MSG_SIZ], *prom;
3740
3741           // str looks something like "Q/a1-a2"; kill the slash
3742           if(str[1] == '/') 
3743                 sprintf(buf, "%c%s", str[0], str+2);
3744           else  strcpy(buf, str); // might be castling
3745           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3746                 strcat(buf, prom); // long move lacks promo specification!
3747           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3748                 if(appData.debugMode) 
3749                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3750                 strcpy(move_str, buf);
3751           }
3752           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3753                                 &fromX, &fromY, &toX, &toY, &promoChar)
3754                || ParseOneMove(buf, moveNum - 1, &moveType,
3755                                 &fromX, &fromY, &toX, &toY, &promoChar);
3756           // end of long SAN patch
3757           if (valid) {
3758             (void) CoordsToAlgebraic(boards[moveNum - 1],
3759                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3760                                      fromY, fromX, toY, toX, promoChar,
3761                                      parseList[moveNum-1]);
3762             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3763                              castlingRights[moveNum]) ) {
3764               case MT_NONE:
3765               case MT_STALEMATE:
3766               default:
3767                 break;
3768               case MT_CHECK:
3769                 if(gameInfo.variant != VariantShogi)
3770                     strcat(parseList[moveNum - 1], "+");
3771                 break;
3772               case MT_CHECKMATE:
3773               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3774                 strcat(parseList[moveNum - 1], "#");
3775                 break;
3776             }
3777             strcat(parseList[moveNum - 1], " ");
3778             strcat(parseList[moveNum - 1], elapsed_time);
3779             /* currentMoveString is set as a side-effect of ParseOneMove */
3780             strcpy(moveList[moveNum - 1], currentMoveString);
3781             strcat(moveList[moveNum - 1], "\n");
3782           } else {
3783             /* Move from ICS was illegal!?  Punt. */
3784   if (appData.debugMode) {
3785     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3786     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3787   }
3788             strcpy(parseList[moveNum - 1], move_str);
3789             strcat(parseList[moveNum - 1], " ");
3790             strcat(parseList[moveNum - 1], elapsed_time);
3791             moveList[moveNum - 1][0] = NULLCHAR;
3792             fromX = fromY = toX = toY = -1;
3793           }
3794         }
3795   if (appData.debugMode) {
3796     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3797     setbuf(debugFP, NULL);
3798   }
3799
3800 #if ZIPPY
3801         /* Send move to chess program (BEFORE animating it). */
3802         if (appData.zippyPlay && !newGame && newMove && 
3803            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3804
3805             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3806                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3807                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3808                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3809                             move_str);
3810                     DisplayError(str, 0);
3811                 } else {
3812                     if (first.sendTime) {
3813                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3814                     }
3815                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3816                     if (firstMove && !bookHit) {
3817                         firstMove = FALSE;
3818                         if (first.useColors) {
3819                           SendToProgram(gameMode == IcsPlayingWhite ?
3820                                         "white\ngo\n" :
3821                                         "black\ngo\n", &first);
3822                         } else {
3823                           SendToProgram("go\n", &first);
3824                         }
3825                         first.maybeThinking = TRUE;
3826