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