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