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