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