Fix expose requests seek graph
[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 PlotSingleSeekAd (int i)
2515 {
2516         PlotSeekAd(i);
2517 }
2518
2519 void
2520 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2521 {
2522         char buf[MSG_SIZ], *ext = "";
2523         VariantClass v = StringToVariant(type);
2524         if(strstr(type, "wild")) {
2525             ext = type + 4; // append wild number
2526             if(v == VariantFischeRandom) type = "chess960"; else
2527             if(v == VariantLoadable) type = "setup"; else
2528             type = VariantName(v);
2529         }
2530         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2531         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2532             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2533             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2534             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2535             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2536             seekNrList[nrOfSeekAds] = nr;
2537             zList[nrOfSeekAds] = 0;
2538             seekAdList[nrOfSeekAds++] = StrSave(buf);
2539             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2540         }
2541 }
2542
2543 void
2544 EraseSeekDot (int i)
2545 {
2546     int x = xList[i], y = yList[i], d=squareSize/4, k;
2547     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2548     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2549     // now replot every dot that overlapped
2550     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2551         int xx = xList[k], yy = yList[k];
2552         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2553             DrawSeekDot(xx, yy, colorList[k]);
2554     }
2555 }
2556
2557 void
2558 RemoveSeekAd (int nr)
2559 {
2560         int i;
2561         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2562             EraseSeekDot(i);
2563             if(seekAdList[i]) free(seekAdList[i]);
2564             seekAdList[i] = seekAdList[--nrOfSeekAds];
2565             seekNrList[i] = seekNrList[nrOfSeekAds];
2566             ratingList[i] = ratingList[nrOfSeekAds];
2567             colorList[i]  = colorList[nrOfSeekAds];
2568             tcList[i] = tcList[nrOfSeekAds];
2569             xList[i]  = xList[nrOfSeekAds];
2570             yList[i]  = yList[nrOfSeekAds];
2571             zList[i]  = zList[nrOfSeekAds];
2572             seekAdList[nrOfSeekAds] = NULL;
2573             break;
2574         }
2575 }
2576
2577 Boolean
2578 MatchSoughtLine (char *line)
2579 {
2580     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2581     int nr, base, inc, u=0; char dummy;
2582
2583     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2584        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2585        (u=1) &&
2586        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2587         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2588         // match: compact and save the line
2589         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2590         return TRUE;
2591     }
2592     return FALSE;
2593 }
2594
2595 int
2596 DrawSeekGraph ()
2597 {
2598     int i;
2599     if(!seekGraphUp) return FALSE;
2600     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2601     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2602
2603     DrawSeekBackground(0, 0, w, h);
2604     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2605     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2606     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2607         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2608         yy = h-1-yy;
2609         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2610         if(i%500 == 0) {
2611             char buf[MSG_SIZ];
2612             snprintf(buf, MSG_SIZ, "%d", i);
2613             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2614         }
2615     }
2616     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2617     for(i=1; i<100; i+=(i<10?1:5)) {
2618         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2619         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2620         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2621             char buf[MSG_SIZ];
2622             snprintf(buf, MSG_SIZ, "%d", i);
2623             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2624         }
2625     }
2626     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2627     return TRUE;
2628 }
2629
2630 int
2631 SeekGraphClick (ClickType click, int x, int y, int moving)
2632 {
2633     static int lastDown = 0, displayed = 0, lastSecond;
2634     if(y < 0) return FALSE;
2635     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2636         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2637         if(!seekGraphUp) return FALSE;
2638         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2639         DrawPosition(TRUE, NULL);
2640         return TRUE;
2641     }
2642     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2643         if(click == Release || moving) return FALSE;
2644         nrOfSeekAds = 0;
2645         soughtPending = TRUE;
2646         SendToICS(ics_prefix);
2647         SendToICS("sought\n"); // should this be "sought all"?
2648     } else { // issue challenge based on clicked ad
2649         int dist = 10000; int i, closest = 0, second = 0;
2650         for(i=0; i<nrOfSeekAds; i++) {
2651             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2652             if(d < dist) { dist = d; closest = i; }
2653             second += (d - zList[i] < 120); // count in-range ads
2654             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2655         }
2656         if(dist < 120) {
2657             char buf[MSG_SIZ];
2658             second = (second > 1);
2659             if(displayed != closest || second != lastSecond) {
2660                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2661                 lastSecond = second; displayed = closest;
2662             }
2663             if(click == Press) {
2664                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2665                 lastDown = closest;
2666                 return TRUE;
2667             } // on press 'hit', only show info
2668             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2669             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2670             SendToICS(ics_prefix);
2671             SendToICS(buf);
2672             return TRUE; // let incoming board of started game pop down the graph
2673         } else if(click == Release) { // release 'miss' is ignored
2674             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2675             if(moving == 2) { // right up-click
2676                 nrOfSeekAds = 0; // refresh graph
2677                 soughtPending = TRUE;
2678                 SendToICS(ics_prefix);
2679                 SendToICS("sought\n"); // should this be "sought all"?
2680             }
2681             return TRUE;
2682         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2683         // press miss or release hit 'pop down' seek graph
2684         seekGraphUp = FALSE;
2685         DrawPosition(TRUE, NULL);
2686     }
2687     return TRUE;
2688 }
2689
2690 void
2691 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2692 {
2693 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2694 #define STARTED_NONE 0
2695 #define STARTED_MOVES 1
2696 #define STARTED_BOARD 2
2697 #define STARTED_OBSERVE 3
2698 #define STARTED_HOLDINGS 4
2699 #define STARTED_CHATTER 5
2700 #define STARTED_COMMENT 6
2701 #define STARTED_MOVES_NOHIDE 7
2702
2703     static int started = STARTED_NONE;
2704     static char parse[20000];
2705     static int parse_pos = 0;
2706     static char buf[BUF_SIZE + 1];
2707     static int firstTime = TRUE, intfSet = FALSE;
2708     static ColorClass prevColor = ColorNormal;
2709     static int savingComment = FALSE;
2710     static int cmatch = 0; // continuation sequence match
2711     char *bp;
2712     char str[MSG_SIZ];
2713     int i, oldi;
2714     int buf_len;
2715     int next_out;
2716     int tkind;
2717     int backup;    /* [DM] For zippy color lines */
2718     char *p;
2719     char talker[MSG_SIZ]; // [HGM] chat
2720     int channel;
2721
2722     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2723
2724     if (appData.debugMode) {
2725       if (!error) {
2726         fprintf(debugFP, "<ICS: ");
2727         show_bytes(debugFP, data, count);
2728         fprintf(debugFP, "\n");
2729       }
2730     }
2731
2732     if (appData.debugMode) { int f = forwardMostMove;
2733         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2734                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2735                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2736     }
2737     if (count > 0) {
2738         /* If last read ended with a partial line that we couldn't parse,
2739            prepend it to the new read and try again. */
2740         if (leftover_len > 0) {
2741             for (i=0; i<leftover_len; i++)
2742               buf[i] = buf[leftover_start + i];
2743         }
2744
2745     /* copy new characters into the buffer */
2746     bp = buf + leftover_len;
2747     buf_len=leftover_len;
2748     for (i=0; i<count; i++)
2749     {
2750         // ignore these
2751         if (data[i] == '\r')
2752             continue;
2753
2754         // join lines split by ICS?
2755         if (!appData.noJoin)
2756         {
2757             /*
2758                 Joining just consists of finding matches against the
2759                 continuation sequence, and discarding that sequence
2760                 if found instead of copying it.  So, until a match
2761                 fails, there's nothing to do since it might be the
2762                 complete sequence, and thus, something we don't want
2763                 copied.
2764             */
2765             if (data[i] == cont_seq[cmatch])
2766             {
2767                 cmatch++;
2768                 if (cmatch == strlen(cont_seq))
2769                 {
2770                     cmatch = 0; // complete match.  just reset the counter
2771
2772                     /*
2773                         it's possible for the ICS to not include the space
2774                         at the end of the last word, making our [correct]
2775                         join operation fuse two separate words.  the server
2776                         does this when the space occurs at the width setting.
2777                     */
2778                     if (!buf_len || buf[buf_len-1] != ' ')
2779                     {
2780                         *bp++ = ' ';
2781                         buf_len++;
2782                     }
2783                 }
2784                 continue;
2785             }
2786             else if (cmatch)
2787             {
2788                 /*
2789                     match failed, so we have to copy what matched before
2790                     falling through and copying this character.  In reality,
2791                     this will only ever be just the newline character, but
2792                     it doesn't hurt to be precise.
2793                 */
2794                 strncpy(bp, cont_seq, cmatch);
2795                 bp += cmatch;
2796                 buf_len += cmatch;
2797                 cmatch = 0;
2798             }
2799         }
2800
2801         // copy this char
2802         *bp++ = data[i];
2803         buf_len++;
2804     }
2805
2806         buf[buf_len] = NULLCHAR;
2807 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2808         next_out = 0;
2809         leftover_start = 0;
2810
2811         i = 0;
2812         while (i < buf_len) {
2813             /* Deal with part of the TELNET option negotiation
2814                protocol.  We refuse to do anything beyond the
2815                defaults, except that we allow the WILL ECHO option,
2816                which ICS uses to turn off password echoing when we are
2817                directly connected to it.  We reject this option
2818                if localLineEditing mode is on (always on in xboard)
2819                and we are talking to port 23, which might be a real
2820                telnet server that will try to keep WILL ECHO on permanently.
2821              */
2822             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2823                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2824                 unsigned char option;
2825                 oldi = i;
2826                 switch ((unsigned char) buf[++i]) {
2827                   case TN_WILL:
2828                     if (appData.debugMode)
2829                       fprintf(debugFP, "\n<WILL ");
2830                     switch (option = (unsigned char) buf[++i]) {
2831                       case TN_ECHO:
2832                         if (appData.debugMode)
2833                           fprintf(debugFP, "ECHO ");
2834                         /* Reply only if this is a change, according
2835                            to the protocol rules. */
2836                         if (remoteEchoOption) break;
2837                         if (appData.localLineEditing &&
2838                             atoi(appData.icsPort) == TN_PORT) {
2839                             TelnetRequest(TN_DONT, TN_ECHO);
2840                         } else {
2841                             EchoOff();
2842                             TelnetRequest(TN_DO, TN_ECHO);
2843                             remoteEchoOption = TRUE;
2844                         }
2845                         break;
2846                       default:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "%d ", option);
2849                         /* Whatever this is, we don't want it. */
2850                         TelnetRequest(TN_DONT, option);
2851                         break;
2852                     }
2853                     break;
2854                   case TN_WONT:
2855                     if (appData.debugMode)
2856                       fprintf(debugFP, "\n<WONT ");
2857                     switch (option = (unsigned char) buf[++i]) {
2858                       case TN_ECHO:
2859                         if (appData.debugMode)
2860                           fprintf(debugFP, "ECHO ");
2861                         /* Reply only if this is a change, according
2862                            to the protocol rules. */
2863                         if (!remoteEchoOption) break;
2864                         EchoOn();
2865                         TelnetRequest(TN_DONT, TN_ECHO);
2866                         remoteEchoOption = FALSE;
2867                         break;
2868                       default:
2869                         if (appData.debugMode)
2870                           fprintf(debugFP, "%d ", (unsigned char) option);
2871                         /* Whatever this is, it must already be turned
2872                            off, because we never agree to turn on
2873                            anything non-default, so according to the
2874                            protocol rules, we don't reply. */
2875                         break;
2876                     }
2877                     break;
2878                   case TN_DO:
2879                     if (appData.debugMode)
2880                       fprintf(debugFP, "\n<DO ");
2881                     switch (option = (unsigned char) buf[++i]) {
2882                       default:
2883                         /* Whatever this is, we refuse to do it. */
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "%d ", option);
2886                         TelnetRequest(TN_WONT, option);
2887                         break;
2888                     }
2889                     break;
2890                   case TN_DONT:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<DONT ");
2893                     switch (option = (unsigned char) buf[++i]) {
2894                       default:
2895                         if (appData.debugMode)
2896                           fprintf(debugFP, "%d ", option);
2897                         /* Whatever this is, we are already not doing
2898                            it, because we never agree to do anything
2899                            non-default, so according to the protocol
2900                            rules, we don't reply. */
2901                         break;
2902                     }
2903                     break;
2904                   case TN_IAC:
2905                     if (appData.debugMode)
2906                       fprintf(debugFP, "\n<IAC ");
2907                     /* Doubled IAC; pass it through */
2908                     i--;
2909                     break;
2910                   default:
2911                     if (appData.debugMode)
2912                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2913                     /* Drop all other telnet commands on the floor */
2914                     break;
2915                 }
2916                 if (oldi > next_out)
2917                   SendToPlayer(&buf[next_out], oldi - next_out);
2918                 if (++i > next_out)
2919                   next_out = i;
2920                 continue;
2921             }
2922
2923             /* OK, this at least will *usually* work */
2924             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2925                 loggedOn = TRUE;
2926             }
2927
2928             if (loggedOn && !intfSet) {
2929                 if (ics_type == ICS_ICC) {
2930                   snprintf(str, MSG_SIZ,
2931                           "/set-quietly interface %s\n/set-quietly style 12\n",
2932                           programVersion);
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2935                 } else if (ics_type == ICS_CHESSNET) {
2936                   snprintf(str, MSG_SIZ, "/style 12\n");
2937                 } else {
2938                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2939                   strcat(str, programVersion);
2940                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2941                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2942                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2943 #ifdef WIN32
2944                   strcat(str, "$iset nohighlight 1\n");
2945 #endif
2946                   strcat(str, "$iset lock 1\n$style 12\n");
2947                 }
2948                 SendToICS(str);
2949                 NotifyFrontendLogin();
2950                 intfSet = TRUE;
2951             }
2952
2953             if (started == STARTED_COMMENT) {
2954                 /* Accumulate characters in comment */
2955                 parse[parse_pos++] = buf[i];
2956                 if (buf[i] == '\n') {
2957                     parse[parse_pos] = NULLCHAR;
2958                     if(chattingPartner>=0) {
2959                         char mess[MSG_SIZ];
2960                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2961                         OutputChatMessage(chattingPartner, mess);
2962                         chattingPartner = -1;
2963                         next_out = i+1; // [HGM] suppress printing in ICS window
2964                     } else
2965                     if(!suppressKibitz) // [HGM] kibitz
2966                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2967                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2968                         int nrDigit = 0, nrAlph = 0, j;
2969                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2970                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2971                         parse[parse_pos] = NULLCHAR;
2972                         // try to be smart: if it does not look like search info, it should go to
2973                         // ICS interaction window after all, not to engine-output window.
2974                         for(j=0; j<parse_pos; j++) { // count letters and digits
2975                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2976                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2977                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2978                         }
2979                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2980                             int depth=0; float score;
2981                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2982                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2983                                 pvInfoList[forwardMostMove-1].depth = depth;
2984                                 pvInfoList[forwardMostMove-1].score = 100*score;
2985                             }
2986                             OutputKibitz(suppressKibitz, parse);
2987                         } else {
2988                             char tmp[MSG_SIZ];
2989                             if(gameMode == IcsObserving) // restore original ICS messages
2990                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2991                             else
2992                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2993                             SendToPlayer(tmp, strlen(tmp));
2994                         }
2995                         next_out = i+1; // [HGM] suppress printing in ICS window
2996                     }
2997                     started = STARTED_NONE;
2998                 } else {
2999                     /* Don't match patterns against characters in comment */
3000                     i++;
3001                     continue;
3002                 }
3003             }
3004             if (started == STARTED_CHATTER) {
3005                 if (buf[i] != '\n') {
3006                     /* Don't match patterns against characters in chatter */
3007                     i++;
3008                     continue;
3009                 }
3010                 started = STARTED_NONE;
3011                 if(suppressKibitz) next_out = i+1;
3012             }
3013
3014             /* Kludge to deal with rcmd protocol */
3015             if (firstTime && looking_at(buf, &i, "\001*")) {
3016                 DisplayFatalError(&buf[1], 0, 1);
3017                 continue;
3018             } else {
3019                 firstTime = FALSE;
3020             }
3021
3022             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3023                 ics_type = ICS_ICC;
3024                 ics_prefix = "/";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3030                 ics_type = ICS_FICS;
3031                 ics_prefix = "$";
3032                 if (appData.debugMode)
3033                   fprintf(debugFP, "ics_type %d\n", ics_type);
3034                 continue;
3035             }
3036             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3037                 ics_type = ICS_CHESSNET;
3038                 ics_prefix = "/";
3039                 if (appData.debugMode)
3040                   fprintf(debugFP, "ics_type %d\n", ics_type);
3041                 continue;
3042             }
3043
3044             if (!loggedOn &&
3045                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3046                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3047                  looking_at(buf, &i, "will be \"*\""))) {
3048               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3049               continue;
3050             }
3051
3052             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3053               char buf[MSG_SIZ];
3054               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3055               DisplayIcsInteractionTitle(buf);
3056               have_set_title = TRUE;
3057             }
3058
3059             /* skip finger notes */
3060             if (started == STARTED_NONE &&
3061                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3062                  (buf[i] == '1' && buf[i+1] == '0')) &&
3063                 buf[i+2] == ':' && buf[i+3] == ' ') {
3064               started = STARTED_CHATTER;
3065               i += 3;
3066               continue;
3067             }
3068
3069             oldi = i;
3070             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3071             if(appData.seekGraph) {
3072                 if(soughtPending && MatchSoughtLine(buf+i)) {
3073                     i = strstr(buf+i, "rated") - buf;
3074                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075                     next_out = leftover_start = i;
3076                     started = STARTED_CHATTER;
3077                     suppressKibitz = TRUE;
3078                     continue;
3079                 }
3080                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3081                         && looking_at(buf, &i, "* ads displayed")) {
3082                     soughtPending = FALSE;
3083                     seekGraphUp = TRUE;
3084                     DrawSeekGraph();
3085                     continue;
3086                 }
3087                 if(appData.autoRefresh) {
3088                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3089                         int s = (ics_type == ICS_ICC); // ICC format differs
3090                         if(seekGraphUp)
3091                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3092                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3093                         looking_at(buf, &i, "*% "); // eat prompt
3094                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3095                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096                         next_out = i; // suppress
3097                         continue;
3098                     }
3099                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3100                         char *p = star_match[0];
3101                         while(*p) {
3102                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3103                             while(*p && *p++ != ' '); // next
3104                         }
3105                         looking_at(buf, &i, "*% "); // eat prompt
3106                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107                         next_out = i;
3108                         continue;
3109                     }
3110                 }
3111             }
3112
3113             /* skip formula vars */
3114             if (started == STARTED_NONE &&
3115                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3116               started = STARTED_CHATTER;
3117               i += 3;
3118               continue;
3119             }
3120
3121             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3122             if (appData.autoKibitz && started == STARTED_NONE &&
3123                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3124                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3125                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3126                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3127                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3128                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3129                         suppressKibitz = TRUE;
3130                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3131                         next_out = i;
3132                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3133                                 && (gameMode == IcsPlayingWhite)) ||
3134                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3135                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3136                             started = STARTED_CHATTER; // own kibitz we simply discard
3137                         else {
3138                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3139                             parse_pos = 0; parse[0] = NULLCHAR;
3140                             savingComment = TRUE;
3141                             suppressKibitz = gameMode != IcsObserving ? 2 :
3142                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3143                         }
3144                         continue;
3145                 } else
3146                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3147                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3148                          && atoi(star_match[0])) {
3149                     // suppress the acknowledgements of our own autoKibitz
3150                     char *p;
3151                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3152                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3153                     SendToPlayer(star_match[0], strlen(star_match[0]));
3154                     if(looking_at(buf, &i, "*% ")) // eat prompt
3155                         suppressKibitz = FALSE;
3156                     next_out = i;
3157                     continue;
3158                 }
3159             } // [HGM] kibitz: end of patch
3160
3161             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3162
3163             // [HGM] chat: intercept tells by users for which we have an open chat window
3164             channel = -1;
3165             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3166                                            looking_at(buf, &i, "* whispers:") ||
3167                                            looking_at(buf, &i, "* kibitzes:") ||
3168                                            looking_at(buf, &i, "* shouts:") ||
3169                                            looking_at(buf, &i, "* c-shouts:") ||
3170                                            looking_at(buf, &i, "--> * ") ||
3171                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3172                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3173                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3174                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3175                 int p;
3176                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3177                 chattingPartner = -1;
3178
3179                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3182                     talker[0] = '['; strcat(talker, "] ");
3183                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3184                     chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3188                 for(p=0; p<MAX_CHAT; p++) {
3189                     if(!strcmp("kibitzes", chatPartner[p])) {
3190                         talker[0] = '['; strcat(talker, "] ");
3191                         chattingPartner = p; break;
3192                     }
3193                 } else
3194                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3195                 for(p=0; p<MAX_CHAT; p++) {
3196                     if(!strcmp("whispers", chatPartner[p])) {
3197                         talker[0] = '['; strcat(talker, "] ");
3198                         chattingPartner = p; break;
3199                     }
3200                 } else
3201                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3202                   if(buf[i-8] == '-' && buf[i-3] == 't')
3203                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3204                     if(!strcmp("c-shouts", chatPartner[p])) {
3205                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3206                         chattingPartner = p; break;
3207                     }
3208                   }
3209                   if(chattingPartner < 0)
3210                   for(p=0; p<MAX_CHAT; p++) {
3211                     if(!strcmp("shouts", chatPartner[p])) {
3212                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3213                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3214                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3215                         chattingPartner = p; break;
3216                     }
3217                   }
3218                 }
3219                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3220                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3221                     talker[0] = 0; Colorize(ColorTell, FALSE);
3222                     chattingPartner = p; break;
3223                 }
3224                 if(chattingPartner<0) i = oldi; else {
3225                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3226                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3227                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3228                     started = STARTED_COMMENT;
3229                     parse_pos = 0; parse[0] = NULLCHAR;
3230                     savingComment = 3 + chattingPartner; // counts as TRUE
3231                     suppressKibitz = TRUE;
3232                     continue;
3233                 }
3234             } // [HGM] chat: end of patch
3235
3236           backup = i;
3237             if (appData.zippyTalk || appData.zippyPlay) {
3238                 /* [DM] Backup address for color zippy lines */
3239 #if ZIPPY
3240                if (loggedOn == TRUE)
3241                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3242                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3243 #endif
3244             } // [DM] 'else { ' deleted
3245                 if (
3246                     /* Regular tells and says */
3247                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3248                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3249                     looking_at(buf, &i, "* says: ") ||
3250                     /* Don't color "message" or "messages" output */
3251                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3252                     looking_at(buf, &i, "*. * at *:*: ") ||
3253                     looking_at(buf, &i, "--* (*:*): ") ||
3254                     /* Message notifications (same color as tells) */
3255                     looking_at(buf, &i, "* has left a message ") ||
3256                     looking_at(buf, &i, "* just sent you a message:\n") ||
3257                     /* Whispers and kibitzes */
3258                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3259                     looking_at(buf, &i, "* kibitzes: ") ||
3260                     /* Channel tells */
3261                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3262
3263                   if (tkind == 1 && strchr(star_match[0], ':')) {
3264                       /* Avoid "tells you:" spoofs in channels */
3265                      tkind = 3;
3266                   }
3267                   if (star_match[0][0] == NULLCHAR ||
3268                       strchr(star_match[0], ' ') ||
3269                       (tkind == 3 && strchr(star_match[1], ' '))) {
3270                     /* Reject bogus matches */
3271                     i = oldi;
3272                   } else {
3273                     if (appData.colorize) {
3274                       if (oldi > next_out) {
3275                         SendToPlayer(&buf[next_out], oldi - next_out);
3276                         next_out = oldi;
3277                       }
3278                       switch (tkind) {
3279                       case 1:
3280                         Colorize(ColorTell, FALSE);
3281                         curColor = ColorTell;
3282                         break;
3283                       case 2:
3284                         Colorize(ColorKibitz, FALSE);
3285                         curColor = ColorKibitz;
3286                         break;
3287                       case 3:
3288                         p = strrchr(star_match[1], '(');
3289                         if (p == NULL) {
3290                           p = star_match[1];
3291                         } else {
3292                           p++;
3293                         }
3294                         if (atoi(p) == 1) {
3295                           Colorize(ColorChannel1, FALSE);
3296                           curColor = ColorChannel1;
3297                         } else {
3298                           Colorize(ColorChannel, FALSE);
3299                           curColor = ColorChannel;
3300                         }
3301                         break;
3302                       case 5:
3303                         curColor = ColorNormal;
3304                         break;
3305                       }
3306                     }
3307                     if (started == STARTED_NONE && appData.autoComment &&
3308                         (gameMode == IcsObserving ||
3309                          gameMode == IcsPlayingWhite ||
3310                          gameMode == IcsPlayingBlack)) {
3311                       parse_pos = i - oldi;
3312                       memcpy(parse, &buf[oldi], parse_pos);
3313                       parse[parse_pos] = NULLCHAR;
3314                       started = STARTED_COMMENT;
3315                       savingComment = TRUE;
3316                     } else {
3317                       started = STARTED_CHATTER;
3318                       savingComment = FALSE;
3319                     }
3320                     loggedOn = TRUE;
3321                     continue;
3322                   }
3323                 }
3324
3325                 if (looking_at(buf, &i, "* s-shouts: ") ||
3326                     looking_at(buf, &i, "* c-shouts: ")) {
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorSShout, FALSE);
3333                         curColor = ColorSShout;
3334                     }
3335                     loggedOn = TRUE;
3336                     started = STARTED_CHATTER;
3337                     continue;
3338                 }
3339
3340                 if (looking_at(buf, &i, "--->")) {
3341                     loggedOn = TRUE;
3342                     continue;
3343                 }
3344
3345                 if (looking_at(buf, &i, "* shouts: ") ||
3346                     looking_at(buf, &i, "--> ")) {
3347                     if (appData.colorize) {
3348                         if (oldi > next_out) {
3349                             SendToPlayer(&buf[next_out], oldi - next_out);
3350                             next_out = oldi;
3351                         }
3352                         Colorize(ColorShout, FALSE);
3353                         curColor = ColorShout;
3354                     }
3355                     loggedOn = TRUE;
3356                     started = STARTED_CHATTER;
3357                     continue;
3358                 }
3359
3360                 if (looking_at( buf, &i, "Challenge:")) {
3361                     if (appData.colorize) {
3362                         if (oldi > next_out) {
3363                             SendToPlayer(&buf[next_out], oldi - next_out);
3364                             next_out = oldi;
3365                         }
3366                         Colorize(ColorChallenge, FALSE);
3367                         curColor = ColorChallenge;
3368                     }
3369                     loggedOn = TRUE;
3370                     continue;
3371                 }
3372
3373                 if (looking_at(buf, &i, "* offers you") ||
3374                     looking_at(buf, &i, "* offers to be") ||
3375                     looking_at(buf, &i, "* would like to") ||
3376                     looking_at(buf, &i, "* requests to") ||
3377                     looking_at(buf, &i, "Your opponent offers") ||
3378                     looking_at(buf, &i, "Your opponent requests")) {
3379
3380                     if (appData.colorize) {
3381                         if (oldi > next_out) {
3382                             SendToPlayer(&buf[next_out], oldi - next_out);
3383                             next_out = oldi;
3384                         }
3385                         Colorize(ColorRequest, FALSE);
3386                         curColor = ColorRequest;
3387                     }
3388                     continue;
3389                 }
3390
3391                 if (looking_at(buf, &i, "* (*) seeking")) {
3392                     if (appData.colorize) {
3393                         if (oldi > next_out) {
3394                             SendToPlayer(&buf[next_out], oldi - next_out);
3395                             next_out = oldi;
3396                         }
3397                         Colorize(ColorSeek, FALSE);
3398                         curColor = ColorSeek;
3399                     }
3400                     continue;
3401             }
3402
3403           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3404
3405             if (looking_at(buf, &i, "\\   ")) {
3406                 if (prevColor != ColorNormal) {
3407                     if (oldi > next_out) {
3408                         SendToPlayer(&buf[next_out], oldi - next_out);
3409                         next_out = oldi;
3410                     }
3411                     Colorize(prevColor, TRUE);
3412                     curColor = prevColor;
3413                 }
3414                 if (savingComment) {
3415                     parse_pos = i - oldi;
3416                     memcpy(parse, &buf[oldi], parse_pos);
3417                     parse[parse_pos] = NULLCHAR;
3418                     started = STARTED_COMMENT;
3419                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3420                         chattingPartner = savingComment - 3; // kludge to remember the box
3421                 } else {
3422                     started = STARTED_CHATTER;
3423                 }
3424                 continue;
3425             }
3426
3427             if (looking_at(buf, &i, "Black Strength :") ||
3428                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3429                 looking_at(buf, &i, "<10>") ||
3430                 looking_at(buf, &i, "#@#")) {
3431                 /* Wrong board style */
3432                 loggedOn = TRUE;
3433                 SendToICS(ics_prefix);
3434                 SendToICS("set style 12\n");
3435                 SendToICS(ics_prefix);
3436                 SendToICS("refresh\n");
3437                 continue;
3438             }
3439
3440             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3441                 ICSInitScript();
3442                 have_sent_ICS_logon = 1;
3443                 continue;
3444             }
3445
3446             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3447                 (looking_at(buf, &i, "\n<12> ") ||
3448                  looking_at(buf, &i, "<12> "))) {
3449                 loggedOn = TRUE;
3450                 if (oldi > next_out) {
3451                     SendToPlayer(&buf[next_out], oldi - next_out);
3452                 }
3453                 next_out = i;
3454                 started = STARTED_BOARD;
3455                 parse_pos = 0;
3456                 continue;
3457             }
3458
3459             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3460                 looking_at(buf, &i, "<b1> ")) {
3461                 if (oldi > next_out) {
3462                     SendToPlayer(&buf[next_out], oldi - next_out);
3463                 }
3464                 next_out = i;
3465                 started = STARTED_HOLDINGS;
3466                 parse_pos = 0;
3467                 continue;
3468             }
3469
3470             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3471                 loggedOn = TRUE;
3472                 /* Header for a move list -- first line */
3473
3474                 switch (ics_getting_history) {
3475                   case H_FALSE:
3476                     switch (gameMode) {
3477                       case IcsIdle:
3478                       case BeginningOfGame:
3479                         /* User typed "moves" or "oldmoves" while we
3480                            were idle.  Pretend we asked for these
3481                            moves and soak them up so user can step
3482                            through them and/or save them.
3483                            */
3484                         Reset(FALSE, TRUE);
3485                         gameMode = IcsObserving;
3486                         ModeHighlight();
3487                         ics_gamenum = -1;
3488                         ics_getting_history = H_GOT_UNREQ_HEADER;
3489                         break;
3490                       case EditGame: /*?*/
3491                       case EditPosition: /*?*/
3492                         /* Should above feature work in these modes too? */
3493                         /* For now it doesn't */
3494                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3495                         break;
3496                       default:
3497                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3498                         break;
3499                     }
3500                     break;
3501                   case H_REQUESTED:
3502                     /* Is this the right one? */
3503                     if (gameInfo.white && gameInfo.black &&
3504                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3505                         strcmp(gameInfo.black, star_match[2]) == 0) {
3506                         /* All is well */
3507                         ics_getting_history = H_GOT_REQ_HEADER;
3508                     }
3509                     break;
3510                   case H_GOT_REQ_HEADER:
3511                   case H_GOT_UNREQ_HEADER:
3512                   case H_GOT_UNWANTED_HEADER:
3513                   case H_GETTING_MOVES:
3514                     /* Should not happen */
3515                     DisplayError(_("Error gathering move list: two headers"), 0);
3516                     ics_getting_history = H_FALSE;
3517                     break;
3518                 }
3519
3520                 /* Save player ratings into gameInfo if needed */
3521                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3522                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3523                     (gameInfo.whiteRating == -1 ||
3524                      gameInfo.blackRating == -1)) {
3525
3526                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3527                     gameInfo.blackRating = string_to_rating(star_match[3]);
3528                     if (appData.debugMode)
3529                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3530                               gameInfo.whiteRating, gameInfo.blackRating);
3531                 }
3532                 continue;
3533             }
3534
3535             if (looking_at(buf, &i,
3536               "* * match, initial time: * minute*, increment: * second")) {
3537                 /* Header for a move list -- second line */
3538                 /* Initial board will follow if this is a wild game */
3539                 if (gameInfo.event != NULL) free(gameInfo.event);
3540                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3541                 gameInfo.event = StrSave(str);
3542                 /* [HGM] we switched variant. Translate boards if needed. */
3543                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3544                 continue;
3545             }
3546
3547             if (looking_at(buf, &i, "Move  ")) {
3548                 /* Beginning of a move list */
3549                 switch (ics_getting_history) {
3550                   case H_FALSE:
3551                     /* Normally should not happen */
3552                     /* Maybe user hit reset while we were parsing */
3553                     break;
3554                   case H_REQUESTED:
3555                     /* Happens if we are ignoring a move list that is not
3556                      * the one we just requested.  Common if the user
3557                      * tries to observe two games without turning off
3558                      * getMoveList */
3559                     break;
3560                   case H_GETTING_MOVES:
3561                     /* Should not happen */
3562                     DisplayError(_("Error gathering move list: nested"), 0);
3563                     ics_getting_history = H_FALSE;
3564                     break;
3565                   case H_GOT_REQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES;
3568                     parse_pos = 0;
3569                     if (oldi > next_out) {
3570                         SendToPlayer(&buf[next_out], oldi - next_out);
3571                     }
3572                     break;
3573                   case H_GOT_UNREQ_HEADER:
3574                     ics_getting_history = H_GETTING_MOVES;
3575                     started = STARTED_MOVES_NOHIDE;
3576                     parse_pos = 0;
3577                     break;
3578                   case H_GOT_UNWANTED_HEADER:
3579                     ics_getting_history = H_FALSE;
3580                     break;
3581                 }
3582                 continue;
3583             }
3584
3585             if (looking_at(buf, &i, "% ") ||
3586                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3587                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3588                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3589                     soughtPending = FALSE;
3590                     seekGraphUp = TRUE;
3591                     DrawSeekGraph();
3592                 }
3593                 if(suppressKibitz) next_out = i;
3594                 savingComment = FALSE;
3595                 suppressKibitz = 0;
3596                 switch (started) {
3597                   case STARTED_MOVES:
3598                   case STARTED_MOVES_NOHIDE:
3599                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3600                     parse[parse_pos + i - oldi] = NULLCHAR;
3601                     ParseGameHistory(parse);
3602 #if ZIPPY
3603                     if (appData.zippyPlay && first.initDone) {
3604                         FeedMovesToProgram(&first, forwardMostMove);
3605                         if (gameMode == IcsPlayingWhite) {
3606                             if (WhiteOnMove(forwardMostMove)) {
3607                                 if (first.sendTime) {
3608                                   if (first.useColors) {
3609                                     SendToProgram("black\n", &first);
3610                                   }
3611                                   SendTimeRemaining(&first, TRUE);
3612                                 }
3613                                 if (first.useColors) {
3614                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3615                                 }
3616                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3617                                 first.maybeThinking = TRUE;
3618                             } else {
3619                                 if (first.usePlayother) {
3620                                   if (first.sendTime) {
3621                                     SendTimeRemaining(&first, TRUE);
3622                                   }
3623                                   SendToProgram("playother\n", &first);
3624                                   firstMove = FALSE;
3625                                 } else {
3626                                   firstMove = TRUE;
3627                                 }
3628                             }
3629                         } else if (gameMode == IcsPlayingBlack) {
3630                             if (!WhiteOnMove(forwardMostMove)) {
3631                                 if (first.sendTime) {
3632                                   if (first.useColors) {
3633                                     SendToProgram("white\n", &first);
3634                                   }
3635                                   SendTimeRemaining(&first, FALSE);
3636                                 }
3637                                 if (first.useColors) {
3638                                   SendToProgram("black\n", &first);
3639                                 }
3640                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3641                                 first.maybeThinking = TRUE;
3642                             } else {
3643                                 if (first.usePlayother) {
3644                                   if (first.sendTime) {
3645                                     SendTimeRemaining(&first, FALSE);
3646                                   }
3647                                   SendToProgram("playother\n", &first);
3648                                   firstMove = FALSE;
3649                                 } else {
3650                                   firstMove = TRUE;
3651                                 }
3652                             }
3653                         }
3654                     }
3655 #endif
3656                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3657                         /* Moves came from oldmoves or moves command
3658                            while we weren't doing anything else.
3659                            */
3660                         currentMove = forwardMostMove;
3661                         ClearHighlights();/*!!could figure this out*/
3662                         flipView = appData.flipView;
3663                         DrawPosition(TRUE, boards[currentMove]);
3664                         DisplayBothClocks();
3665                         snprintf(str, MSG_SIZ, "%s %s %s",
3666                                 gameInfo.white, _("vs."),  gameInfo.black);
3667                         DisplayTitle(str);
3668                         gameMode = IcsIdle;
3669                     } else {
3670                         /* Moves were history of an active game */
3671                         if (gameInfo.resultDetails != NULL) {
3672                             free(gameInfo.resultDetails);
3673                             gameInfo.resultDetails = NULL;
3674                         }
3675                     }
3676                     HistorySet(parseList, backwardMostMove,
3677                                forwardMostMove, currentMove-1);
3678                     DisplayMove(currentMove - 1);
3679                     if (started == STARTED_MOVES) next_out = i;
3680                     started = STARTED_NONE;
3681                     ics_getting_history = H_FALSE;
3682                     break;
3683
3684                   case STARTED_OBSERVE:
3685                     started = STARTED_NONE;
3686                     SendToICS(ics_prefix);
3687                     SendToICS("refresh\n");
3688                     break;
3689
3690                   default:
3691                     break;
3692                 }
3693                 if(bookHit) { // [HGM] book: simulate book reply
3694                     static char bookMove[MSG_SIZ]; // a bit generous?
3695
3696                     programStats.nodes = programStats.depth = programStats.time =
3697                     programStats.score = programStats.got_only_move = 0;
3698                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3699
3700                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3701                     strcat(bookMove, bookHit);
3702                     HandleMachineMove(bookMove, &first);
3703                 }
3704                 continue;
3705             }
3706
3707             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3708                  started == STARTED_HOLDINGS ||
3709                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3710                 /* Accumulate characters in move list or board */
3711                 parse[parse_pos++] = buf[i];
3712             }
3713
3714             /* Start of game messages.  Mostly we detect start of game
3715                when the first board image arrives.  On some versions
3716                of the ICS, though, we need to do a "refresh" after starting
3717                to observe in order to get the current board right away. */
3718             if (looking_at(buf, &i, "Adding game * to observation list")) {
3719                 started = STARTED_OBSERVE;
3720                 continue;
3721             }
3722
3723             /* Handle auto-observe */
3724             if (appData.autoObserve &&
3725                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3726                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3727                 char *player;
3728                 /* Choose the player that was highlighted, if any. */
3729                 if (star_match[0][0] == '\033' ||
3730                     star_match[1][0] != '\033') {
3731                     player = star_match[0];
3732                 } else {
3733                     player = star_match[2];
3734                 }
3735                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3736                         ics_prefix, StripHighlightAndTitle(player));
3737                 SendToICS(str);
3738
3739                 /* Save ratings from notify string */
3740                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3741                 player1Rating = string_to_rating(star_match[1]);
3742                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3743                 player2Rating = string_to_rating(star_match[3]);
3744
3745                 if (appData.debugMode)
3746                   fprintf(debugFP,
3747                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3748                           player1Name, player1Rating,
3749                           player2Name, player2Rating);
3750
3751                 continue;
3752             }
3753
3754             /* Deal with automatic examine mode after a game,
3755                and with IcsObserving -> IcsExamining transition */
3756             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3757                 looking_at(buf, &i, "has made you an examiner of game *")) {
3758
3759                 int gamenum = atoi(star_match[0]);
3760                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3761                     gamenum == ics_gamenum) {
3762                     /* We were already playing or observing this game;
3763                        no need to refetch history */
3764                     gameMode = IcsExamining;
3765                     if (pausing) {
3766                         pauseExamForwardMostMove = forwardMostMove;
3767                     } else if (currentMove < forwardMostMove) {
3768                         ForwardInner(forwardMostMove);
3769                     }
3770                 } else {
3771                     /* I don't think this case really can happen */
3772                     SendToICS(ics_prefix);
3773                     SendToICS("refresh\n");
3774                 }
3775                 continue;
3776             }
3777
3778             /* Error messages */
3779 //          if (ics_user_moved) {
3780             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3781                 if (looking_at(buf, &i, "Illegal move") ||
3782                     looking_at(buf, &i, "Not a legal move") ||
3783                     looking_at(buf, &i, "Your king is in check") ||
3784                     looking_at(buf, &i, "It isn't your turn") ||
3785                     looking_at(buf, &i, "It is not your move")) {
3786                     /* Illegal move */
3787                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3788                         currentMove = forwardMostMove-1;
3789                         DisplayMove(currentMove - 1); /* before DMError */
3790                         DrawPosition(FALSE, boards[currentMove]);
3791                         SwitchClocks(forwardMostMove-1); // [HGM] race
3792                         DisplayBothClocks();
3793                     }
3794                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3795                     ics_user_moved = 0;
3796                     continue;
3797                 }
3798             }
3799
3800             if (looking_at(buf, &i, "still have time") ||
3801                 looking_at(buf, &i, "not out of time") ||
3802                 looking_at(buf, &i, "either player is out of time") ||
3803                 looking_at(buf, &i, "has timeseal; checking")) {
3804                 /* We must have called his flag a little too soon */
3805                 whiteFlag = blackFlag = FALSE;
3806                 continue;
3807             }
3808
3809             if (looking_at(buf, &i, "added * seconds to") ||
3810                 looking_at(buf, &i, "seconds were added to")) {
3811                 /* Update the clocks */
3812                 SendToICS(ics_prefix);
3813                 SendToICS("refresh\n");
3814                 continue;
3815             }
3816
3817             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3818                 ics_clock_paused = TRUE;
3819                 StopClocks();
3820                 continue;
3821             }
3822
3823             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3824                 ics_clock_paused = FALSE;
3825                 StartClocks();
3826                 continue;
3827             }
3828
3829             /* Grab player ratings from the Creating: message.
3830                Note we have to check for the special case when
3831                the ICS inserts things like [white] or [black]. */
3832             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3833                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3834                 /* star_matches:
3835                    0    player 1 name (not necessarily white)
3836                    1    player 1 rating
3837                    2    empty, white, or black (IGNORED)
3838                    3    player 2 name (not necessarily black)
3839                    4    player 2 rating
3840
3841                    The names/ratings are sorted out when the game
3842                    actually starts (below).
3843                 */
3844                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3845                 player1Rating = string_to_rating(star_match[1]);
3846                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3847                 player2Rating = string_to_rating(star_match[4]);
3848
3849                 if (appData.debugMode)
3850                   fprintf(debugFP,
3851                           "Ratings from 'Creating:' %s %d, %s %d\n",
3852                           player1Name, player1Rating,
3853                           player2Name, player2Rating);
3854
3855                 continue;
3856             }
3857
3858             /* Improved generic start/end-of-game messages */
3859             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3860                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3861                 /* If tkind == 0: */
3862                 /* star_match[0] is the game number */
3863                 /*           [1] is the white player's name */
3864                 /*           [2] is the black player's name */
3865                 /* For end-of-game: */
3866