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