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