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