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