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