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