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