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