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