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