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