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