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