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