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