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