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