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