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