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