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