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