Add -first/secondPgnName option
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236
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, useNick;
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 -fn \"\"");
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(!nickName[0]) useNick = FALSE;
899     if(useNick) ASSIGN(appData.pgnName[i], nickName);
900     if(addToList) {
901         int len;
902         q = firstChessProgramNames;
903         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
904         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
905                         useNick ? " -fn \"" : "",
906                         useNick ? nickName : "",
907                         useNick ? "\"" : "",
908                         v1 ? " -firstProtocolVersion 1" : "",
909                         hasBook ? "" : " -fNoOwnBookUCI",
910                         isUCI ? " -fUCI" : "",
911                         storeVariant ? " -variant " : "",
912                         storeVariant ? VariantName(gameInfo.variant) : "");
913         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
914         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
915         if(q)   free(q);
916     }
917     ReplaceEngine(cps, i);
918 }
919
920 void
921 InitTimeControls()
922 {
923     int matched, min, sec;
924     /*
925      * Parse timeControl resource
926      */
927     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
928                           appData.movesPerSession)) {
929         char buf[MSG_SIZ];
930         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
931         DisplayFatalError(buf, 0, 2);
932     }
933
934     /*
935      * Parse searchTime resource
936      */
937     if (*appData.searchTime != NULLCHAR) {
938         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
939         if (matched == 1) {
940             searchTime = min * 60;
941         } else if (matched == 2) {
942             searchTime = min * 60 + sec;
943         } else {
944             char buf[MSG_SIZ];
945             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
946             DisplayFatalError(buf, 0, 2);
947         }
948     }
949 }
950
951 void
952 InitBackEnd1()
953 {
954
955     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
956     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
957
958     GetTimeMark(&programStartTime);
959     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
960     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
961
962     ClearProgramStats();
963     programStats.ok_to_send = 1;
964     programStats.seen_stat = 0;
965
966     /*
967      * Initialize game list
968      */
969     ListNew(&gameList);
970
971
972     /*
973      * Internet chess server status
974      */
975     if (appData.icsActive) {
976         appData.matchMode = FALSE;
977         appData.matchGames = 0;
978 #if ZIPPY
979         appData.noChessProgram = !appData.zippyPlay;
980 #else
981         appData.zippyPlay = FALSE;
982         appData.zippyTalk = FALSE;
983         appData.noChessProgram = TRUE;
984 #endif
985         if (*appData.icsHelper != NULLCHAR) {
986             appData.useTelnet = TRUE;
987             appData.telnetProgram = appData.icsHelper;
988         }
989     } else {
990         appData.zippyTalk = appData.zippyPlay = FALSE;
991     }
992
993     /* [AS] Initialize pv info list [HGM] and game state */
994     {
995         int i, j;
996
997         for( i=0; i<=framePtr; i++ ) {
998             pvInfoList[i].depth = -1;
999             boards[i][EP_STATUS] = EP_NONE;
1000             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1001         }
1002     }
1003
1004     InitTimeControls();
1005
1006     /* [AS] Adjudication threshold */
1007     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1008
1009     InitEngine(&first, 0);
1010     InitEngine(&second, 1);
1011     CommonEngineInit();
1012
1013     if (appData.icsActive) {
1014         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1015     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1016         appData.clockMode = FALSE;
1017         first.sendTime = second.sendTime = 0;
1018     }
1019
1020 #if ZIPPY
1021     /* Override some settings from environment variables, for backward
1022        compatibility.  Unfortunately it's not feasible to have the env
1023        vars just set defaults, at least in xboard.  Ugh.
1024     */
1025     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1026       ZippyInit();
1027     }
1028 #endif
1029
1030     if (!appData.icsActive) {
1031       char buf[MSG_SIZ];
1032       int len;
1033
1034       /* Check for variants that are supported only in ICS mode,
1035          or not at all.  Some that are accepted here nevertheless
1036          have bugs; see comments below.
1037       */
1038       VariantClass variant = StringToVariant(appData.variant);
1039       switch (variant) {
1040       case VariantBughouse:     /* need four players and two boards */
1041       case VariantKriegspiel:   /* need to hide pieces and move details */
1042         /* case VariantFischeRandom: (Fabien: moved below) */
1043         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1044         if( (len > MSG_SIZ) && appData.debugMode )
1045           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1046
1047         DisplayFatalError(buf, 0, 2);
1048         return;
1049
1050       case VariantUnknown:
1051       case VariantLoadable:
1052       case Variant29:
1053       case Variant30:
1054       case Variant31:
1055       case Variant32:
1056       case Variant33:
1057       case Variant34:
1058       case Variant35:
1059       case Variant36:
1060       default:
1061         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1062         if( (len > MSG_SIZ) && appData.debugMode )
1063           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1064
1065         DisplayFatalError(buf, 0, 2);
1066         return;
1067
1068       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1069       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1070       case VariantGothic:     /* [HGM] should work */
1071       case VariantCapablanca: /* [HGM] should work */
1072       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1073       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1074       case VariantKnightmate: /* [HGM] should work */
1075       case VariantCylinder:   /* [HGM] untested */
1076       case VariantFalcon:     /* [HGM] untested */
1077       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1078                                  offboard interposition not understood */
1079       case VariantNormal:     /* definitely works! */
1080       case VariantWildCastle: /* pieces not automatically shuffled */
1081       case VariantNoCastle:   /* pieces not automatically shuffled */
1082       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1083       case VariantLosers:     /* should work except for win condition,
1084                                  and doesn't know captures are mandatory */
1085       case VariantSuicide:    /* should work except for win condition,
1086                                  and doesn't know captures are mandatory */
1087       case VariantGiveaway:   /* should work except for win condition,
1088                                  and doesn't know captures are mandatory */
1089       case VariantTwoKings:   /* should work */
1090       case VariantAtomic:     /* should work except for win condition */
1091       case Variant3Check:     /* should work except for win condition */
1092       case VariantShatranj:   /* should work except for all win conditions */
1093       case VariantMakruk:     /* should work except for daw countdown */
1094       case VariantBerolina:   /* might work if TestLegality is off */
1095       case VariantCapaRandom: /* should work */
1096       case VariantJanus:      /* should work */
1097       case VariantSuper:      /* experimental */
1098       case VariantGreat:      /* experimental, requires legality testing to be off */
1099       case VariantSChess:     /* S-Chess, should work */
1100       case VariantSpartan:    /* should work */
1101         break;
1102       }
1103     }
1104
1105 }
1106
1107 int NextIntegerFromString( char ** str, long * value )
1108 {
1109     int result = -1;
1110     char * s = *str;
1111
1112     while( *s == ' ' || *s == '\t' ) {
1113         s++;
1114     }
1115
1116     *value = 0;
1117
1118     if( *s >= '0' && *s <= '9' ) {
1119         while( *s >= '0' && *s <= '9' ) {
1120             *value = *value * 10 + (*s - '0');
1121             s++;
1122         }
1123
1124         result = 0;
1125     }
1126
1127     *str = s;
1128
1129     return result;
1130 }
1131
1132 int NextTimeControlFromString( char ** str, long * value )
1133 {
1134     long temp;
1135     int result = NextIntegerFromString( str, &temp );
1136
1137     if( result == 0 ) {
1138         *value = temp * 60; /* Minutes */
1139         if( **str == ':' ) {
1140             (*str)++;
1141             result = NextIntegerFromString( str, &temp );
1142             *value += temp; /* Seconds */
1143         }
1144     }
1145
1146     return result;
1147 }
1148
1149 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1150 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1151     int result = -1, type = 0; long temp, temp2;
1152
1153     if(**str != ':') return -1; // old params remain in force!
1154     (*str)++;
1155     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1156     if( NextIntegerFromString( str, &temp ) ) return -1;
1157     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1158
1159     if(**str != '/') {
1160         /* time only: incremental or sudden-death time control */
1161         if(**str == '+') { /* increment follows; read it */
1162             (*str)++;
1163             if(**str == '!') type = *(*str)++; // Bronstein TC
1164             if(result = NextIntegerFromString( str, &temp2)) return -1;
1165             *inc = temp2 * 1000;
1166             if(**str == '.') { // read fraction of increment
1167                 char *start = ++(*str);
1168                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1169                 temp2 *= 1000;
1170                 while(start++ < *str) temp2 /= 10;
1171                 *inc += temp2;
1172             }
1173         } else *inc = 0;
1174         *moves = 0; *tc = temp * 1000; *incType = type;
1175         return 0;
1176     }
1177
1178     (*str)++; /* classical time control */
1179     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1180
1181     if(result == 0) {
1182         *moves = temp;
1183         *tc    = temp2 * 1000;
1184         *inc   = 0;
1185         *incType = type;
1186     }
1187     return result;
1188 }
1189
1190 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1191 {   /* [HGM] get time to add from the multi-session time-control string */
1192     int incType, moves=1; /* kludge to force reading of first session */
1193     long time, increment;
1194     char *s = tcString;
1195
1196     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1197     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1198     do {
1199         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1200         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1201         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1202         if(movenr == -1) return time;    /* last move before new session     */
1203         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1204         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1205         if(!moves) return increment;     /* current session is incremental   */
1206         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1207     } while(movenr >= -1);               /* try again for next session       */
1208
1209     return 0; // no new time quota on this move
1210 }
1211
1212 int
1213 ParseTimeControl(tc, ti, mps)
1214      char *tc;
1215      float ti;
1216      int mps;
1217 {
1218   long tc1;
1219   long tc2;
1220   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1221   int min, sec=0;
1222
1223   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1224   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1225       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1226   if(ti > 0) {
1227
1228     if(mps)
1229       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1230     else 
1231       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1232   } else {
1233     if(mps)
1234       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1235     else 
1236       snprintf(buf, MSG_SIZ, ":%s", mytc);
1237   }
1238   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1239   
1240   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1241     return FALSE;
1242   }
1243
1244   if( *tc == '/' ) {
1245     /* Parse second time control */
1246     tc++;
1247
1248     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1249       return FALSE;
1250     }
1251
1252     if( tc2 == 0 ) {
1253       return FALSE;
1254     }
1255
1256     timeControl_2 = tc2 * 1000;
1257   }
1258   else {
1259     timeControl_2 = 0;
1260   }
1261
1262   if( tc1 == 0 ) {
1263     return FALSE;
1264   }
1265
1266   timeControl = tc1 * 1000;
1267
1268   if (ti >= 0) {
1269     timeIncrement = ti * 1000;  /* convert to ms */
1270     movesPerSession = 0;
1271   } else {
1272     timeIncrement = 0;
1273     movesPerSession = mps;
1274   }
1275   return TRUE;
1276 }
1277
1278 void
1279 InitBackEnd2()
1280 {
1281     if (appData.debugMode) {
1282         fprintf(debugFP, "%s\n", programVersion);
1283     }
1284
1285     set_cont_sequence(appData.wrapContSeq);
1286     if (appData.matchGames > 0) {
1287         appData.matchMode = TRUE;
1288     } else if (appData.matchMode) {
1289         appData.matchGames = 1;
1290     }
1291     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1292         appData.matchGames = appData.sameColorGames;
1293     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1294         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1295         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1296     }
1297     Reset(TRUE, FALSE);
1298     if (appData.noChessProgram || first.protocolVersion == 1) {
1299       InitBackEnd3();
1300     } else {
1301       /* kludge: allow timeout for initial "feature" commands */
1302       FreezeUI();
1303       DisplayMessage("", _("Starting chess program"));
1304       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1305     }
1306 }
1307
1308 int
1309 CalculateIndex(int index, int gameNr)
1310 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1311     int res;
1312     if(index > 0) return index; // fixed nmber
1313     if(index == 0) return 1;
1314     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1315     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1316     return res;
1317 }
1318
1319 int
1320 LoadGameOrPosition(int gameNr)
1321 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1322     if (*appData.loadGameFile != NULLCHAR) {
1323         if (!LoadGameFromFile(appData.loadGameFile,
1324                 CalculateIndex(appData.loadGameIndex, gameNr),
1325                               appData.loadGameFile, FALSE)) {
1326             DisplayFatalError(_("Bad game file"), 0, 1);
1327             return 0;
1328         }
1329     } else if (*appData.loadPositionFile != NULLCHAR) {
1330         if (!LoadPositionFromFile(appData.loadPositionFile,
1331                 CalculateIndex(appData.loadPositionIndex, gameNr),
1332                                   appData.loadPositionFile)) {
1333             DisplayFatalError(_("Bad position file"), 0, 1);
1334             return 0;
1335         }
1336     }
1337     return 1;
1338 }
1339
1340 void
1341 ReserveGame(int gameNr, char resChar)
1342 {
1343     FILE *tf = fopen(appData.tourneyFile, "r+");
1344     char *p, *q, c, buf[MSG_SIZ];
1345     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1346     safeStrCpy(buf, lastMsg, MSG_SIZ);
1347     DisplayMessage(_("Pick new game"), "");
1348     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1349     ParseArgsFromFile(tf);
1350     p = q = appData.results;
1351     if(appData.debugMode) {
1352       char *r = appData.participants;
1353       fprintf(debugFP, "results = '%s'\n", p);
1354       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1355       fprintf(debugFP, "\n");
1356     }
1357     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1358     nextGame = q - p;
1359     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1360     safeStrCpy(q, p, strlen(p) + 2);
1361     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1362     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1363     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1364         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1365         q[nextGame] = '*';
1366     }
1367     fseek(tf, -(strlen(p)+4), SEEK_END);
1368     c = fgetc(tf);
1369     if(c != '"') // depending on DOS or Unix line endings we can be one off
1370          fseek(tf, -(strlen(p)+2), SEEK_END);
1371     else fseek(tf, -(strlen(p)+3), SEEK_END);
1372     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1373     DisplayMessage(buf, "");
1374     free(p); appData.results = q;
1375     if(nextGame <= appData.matchGames && resChar != ' ' &&
1376        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1377         UnloadEngine(&first);  // next game belongs to other pairing;
1378         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1379     }
1380 }
1381
1382 void
1383 MatchEvent(int mode)
1384 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1385         int dummy;
1386         if(matchMode) { // already in match mode: switch it off
1387             abortMatch = TRUE;
1388             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1389             ModeHighlight(); // kludgey way to remove checkmark...
1390             return;
1391         }
1392 //      if(gameMode != BeginningOfGame) {
1393 //          DisplayError(_("You can only start a match from the initial position."), 0);
1394 //          return;
1395 //      }
1396         abortMatch = FALSE;
1397         appData.matchGames = appData.defaultMatchGames;
1398         /* Set up machine vs. machine match */
1399         nextGame = 0;
1400         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1401         if(appData.tourneyFile[0]) {
1402             ReserveGame(-1, 0);
1403             if(nextGame > appData.matchGames) {
1404                 char buf[MSG_SIZ];
1405                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1406                 DisplayError(buf, 0);
1407                 appData.tourneyFile[0] = 0;
1408                 return;
1409             }
1410         } else
1411         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1412             DisplayFatalError(_("Can't have a match with no chess programs"),
1413                               0, 2);
1414             return;
1415         }
1416         matchMode = mode;
1417         matchGame = roundNr = 1;
1418         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1419         NextMatchGame();
1420 }
1421
1422 void
1423 InitBackEnd3 P((void))
1424 {
1425     GameMode initialMode;
1426     char buf[MSG_SIZ];
1427     int err, len;
1428
1429     InitChessProgram(&first, startedFromSetupPosition);
1430
1431     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1432         free(programVersion);
1433         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1434         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1435     }
1436
1437     if (appData.icsActive) {
1438 #ifdef WIN32
1439         /* [DM] Make a console window if needed [HGM] merged ifs */
1440         ConsoleCreate();
1441 #endif
1442         err = establish();
1443         if (err != 0)
1444           {
1445             if (*appData.icsCommPort != NULLCHAR)
1446               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1447                              appData.icsCommPort);
1448             else
1449               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1450                         appData.icsHost, appData.icsPort);
1451
1452             if( (len > MSG_SIZ) && appData.debugMode )
1453               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1454
1455             DisplayFatalError(buf, err, 1);
1456             return;
1457         }
1458         SetICSMode();
1459         telnetISR =
1460           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1461         fromUserISR =
1462           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1463         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1464             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1465     } else if (appData.noChessProgram) {
1466         SetNCPMode();
1467     } else {
1468         SetGNUMode();
1469     }
1470
1471     if (*appData.cmailGameName != NULLCHAR) {
1472         SetCmailMode();
1473         OpenLoopback(&cmailPR);
1474         cmailISR =
1475           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1476     }
1477
1478     ThawUI();
1479     DisplayMessage("", "");
1480     if (StrCaseCmp(appData.initialMode, "") == 0) {
1481       initialMode = BeginningOfGame;
1482       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1483         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1484         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1485         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1486         ModeHighlight();
1487       }
1488     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1489       initialMode = TwoMachinesPlay;
1490     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1491       initialMode = AnalyzeFile;
1492     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1493       initialMode = AnalyzeMode;
1494     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1495       initialMode = MachinePlaysWhite;
1496     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1497       initialMode = MachinePlaysBlack;
1498     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1499       initialMode = EditGame;
1500     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1501       initialMode = EditPosition;
1502     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1503       initialMode = Training;
1504     } else {
1505       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1506       if( (len > MSG_SIZ) && appData.debugMode )
1507         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1508
1509       DisplayFatalError(buf, 0, 2);
1510       return;
1511     }
1512
1513     if (appData.matchMode) {
1514         if(appData.tourneyFile[0]) { // start tourney from command line
1515             FILE *f;
1516             if(f = fopen(appData.tourneyFile, "r")) {
1517                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1518                 fclose(f);
1519             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1520         }
1521         MatchEvent(TRUE);
1522     } else if (*appData.cmailGameName != NULLCHAR) {
1523         /* Set up cmail mode */
1524         ReloadCmailMsgEvent(TRUE);
1525     } else {
1526         /* Set up other modes */
1527         if (initialMode == AnalyzeFile) {
1528           if (*appData.loadGameFile == NULLCHAR) {
1529             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1530             return;
1531           }
1532         }
1533         if (*appData.loadGameFile != NULLCHAR) {
1534             (void) LoadGameFromFile(appData.loadGameFile,
1535                                     appData.loadGameIndex,
1536                                     appData.loadGameFile, TRUE);
1537         } else if (*appData.loadPositionFile != NULLCHAR) {
1538             (void) LoadPositionFromFile(appData.loadPositionFile,
1539                                         appData.loadPositionIndex,
1540                                         appData.loadPositionFile);
1541             /* [HGM] try to make self-starting even after FEN load */
1542             /* to allow automatic setup of fairy variants with wtm */
1543             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1544                 gameMode = BeginningOfGame;
1545                 setboardSpoiledMachineBlack = 1;
1546             }
1547             /* [HGM] loadPos: make that every new game uses the setup */
1548             /* from file as long as we do not switch variant          */
1549             if(!blackPlaysFirst) {
1550                 startedFromPositionFile = TRUE;
1551                 CopyBoard(filePosition, boards[0]);
1552             }
1553         }
1554         if (initialMode == AnalyzeMode) {
1555           if (appData.noChessProgram) {
1556             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1557             return;
1558           }
1559           if (appData.icsActive) {
1560             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1561             return;
1562           }
1563           AnalyzeModeEvent();
1564         } else if (initialMode == AnalyzeFile) {
1565           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1566           ShowThinkingEvent();
1567           AnalyzeFileEvent();
1568           AnalysisPeriodicEvent(1);
1569         } else if (initialMode == MachinePlaysWhite) {
1570           if (appData.noChessProgram) {
1571             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1572                               0, 2);
1573             return;
1574           }
1575           if (appData.icsActive) {
1576             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1577                               0, 2);
1578             return;
1579           }
1580           MachineWhiteEvent();
1581         } else if (initialMode == MachinePlaysBlack) {
1582           if (appData.noChessProgram) {
1583             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1584                               0, 2);
1585             return;
1586           }
1587           if (appData.icsActive) {
1588             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1589                               0, 2);
1590             return;
1591           }
1592           MachineBlackEvent();
1593         } else if (initialMode == TwoMachinesPlay) {
1594           if (appData.noChessProgram) {
1595             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1596                               0, 2);
1597             return;
1598           }
1599           if (appData.icsActive) {
1600             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1601                               0, 2);
1602             return;
1603           }
1604           TwoMachinesEvent();
1605         } else if (initialMode == EditGame) {
1606           EditGameEvent();
1607         } else if (initialMode == EditPosition) {
1608           EditPositionEvent();
1609         } else if (initialMode == Training) {
1610           if (*appData.loadGameFile == NULLCHAR) {
1611             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1612             return;
1613           }
1614           TrainingEvent();
1615         }
1616     }
1617 }
1618
1619 /*
1620  * Establish will establish a contact to a remote host.port.
1621  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1622  *  used to talk to the host.
1623  * Returns 0 if okay, error code if not.
1624  */
1625 int
1626 establish()
1627 {
1628     char buf[MSG_SIZ];
1629
1630     if (*appData.icsCommPort != NULLCHAR) {
1631         /* Talk to the host through a serial comm port */
1632         return OpenCommPort(appData.icsCommPort, &icsPR);
1633
1634     } else if (*appData.gateway != NULLCHAR) {
1635         if (*appData.remoteShell == NULLCHAR) {
1636             /* Use the rcmd protocol to run telnet program on a gateway host */
1637             snprintf(buf, sizeof(buf), "%s %s %s",
1638                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1639             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1640
1641         } else {
1642             /* Use the rsh program to run telnet program on a gateway host */
1643             if (*appData.remoteUser == NULLCHAR) {
1644                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1645                         appData.gateway, appData.telnetProgram,
1646                         appData.icsHost, appData.icsPort);
1647             } else {
1648                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1649                         appData.remoteShell, appData.gateway,
1650                         appData.remoteUser, appData.telnetProgram,
1651                         appData.icsHost, appData.icsPort);
1652             }
1653             return StartChildProcess(buf, "", &icsPR);
1654
1655         }
1656     } else if (appData.useTelnet) {
1657         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1658
1659     } else {
1660         /* TCP socket interface differs somewhat between
1661            Unix and NT; handle details in the front end.
1662            */
1663         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1664     }
1665 }
1666
1667 void EscapeExpand(char *p, char *q)
1668 {       // [HGM] initstring: routine to shape up string arguments
1669         while(*p++ = *q++) if(p[-1] == '\\')
1670             switch(*q++) {
1671                 case 'n': p[-1] = '\n'; break;
1672                 case 'r': p[-1] = '\r'; break;
1673                 case 't': p[-1] = '\t'; break;
1674                 case '\\': p[-1] = '\\'; break;
1675                 case 0: *p = 0; return;
1676                 default: p[-1] = q[-1]; break;
1677             }
1678 }
1679
1680 void
1681 show_bytes(fp, buf, count)
1682      FILE *fp;
1683      char *buf;
1684      int count;
1685 {
1686     while (count--) {
1687         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1688             fprintf(fp, "\\%03o", *buf & 0xff);
1689         } else {
1690             putc(*buf, fp);
1691         }
1692         buf++;
1693     }
1694     fflush(fp);
1695 }
1696
1697 /* Returns an errno value */
1698 int
1699 OutputMaybeTelnet(pr, message, count, outError)
1700      ProcRef pr;
1701      char *message;
1702      int count;
1703      int *outError;
1704 {
1705     char buf[8192], *p, *q, *buflim;
1706     int left, newcount, outcount;
1707
1708     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1709         *appData.gateway != NULLCHAR) {
1710         if (appData.debugMode) {
1711             fprintf(debugFP, ">ICS: ");
1712             show_bytes(debugFP, message, count);
1713             fprintf(debugFP, "\n");
1714         }
1715         return OutputToProcess(pr, message, count, outError);
1716     }
1717
1718     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1719     p = message;
1720     q = buf;
1721     left = count;
1722     newcount = 0;
1723     while (left) {
1724         if (q >= buflim) {
1725             if (appData.debugMode) {
1726                 fprintf(debugFP, ">ICS: ");
1727                 show_bytes(debugFP, buf, newcount);
1728                 fprintf(debugFP, "\n");
1729             }
1730             outcount = OutputToProcess(pr, buf, newcount, outError);
1731             if (outcount < newcount) return -1; /* to be sure */
1732             q = buf;
1733             newcount = 0;
1734         }
1735         if (*p == '\n') {
1736             *q++ = '\r';
1737             newcount++;
1738         } else if (((unsigned char) *p) == TN_IAC) {
1739             *q++ = (char) TN_IAC;
1740             newcount ++;
1741         }
1742         *q++ = *p++;
1743         newcount++;
1744         left--;
1745     }
1746     if (appData.debugMode) {
1747         fprintf(debugFP, ">ICS: ");
1748         show_bytes(debugFP, buf, newcount);
1749         fprintf(debugFP, "\n");
1750     }
1751     outcount = OutputToProcess(pr, buf, newcount, outError);
1752     if (outcount < newcount) return -1; /* to be sure */
1753     return count;
1754 }
1755
1756 void
1757 read_from_player(isr, closure, message, count, error)
1758      InputSourceRef isr;
1759      VOIDSTAR closure;
1760      char *message;
1761      int count;
1762      int error;
1763 {
1764     int outError, outCount;
1765     static int gotEof = 0;
1766
1767     /* Pass data read from player on to ICS */
1768     if (count > 0) {
1769         gotEof = 0;
1770         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1771         if (outCount < count) {
1772             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1773         }
1774     } else if (count < 0) {
1775         RemoveInputSource(isr);
1776         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1777     } else if (gotEof++ > 0) {
1778         RemoveInputSource(isr);
1779         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1780     }
1781 }
1782
1783 void
1784 KeepAlive()
1785 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1786     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1787     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1788     SendToICS("date\n");
1789     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1790 }
1791
1792 /* added routine for printf style output to ics */
1793 void ics_printf(char *format, ...)
1794 {
1795     char buffer[MSG_SIZ];
1796     va_list args;
1797
1798     va_start(args, format);
1799     vsnprintf(buffer, sizeof(buffer), format, args);
1800     buffer[sizeof(buffer)-1] = '\0';
1801     SendToICS(buffer);
1802     va_end(args);
1803 }
1804
1805 void
1806 SendToICS(s)
1807      char *s;
1808 {
1809     int count, outCount, outError;
1810
1811     if (icsPR == NULL) return;
1812
1813     count = strlen(s);
1814     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1815     if (outCount < count) {
1816         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1817     }
1818 }
1819
1820 /* This is used for sending logon scripts to the ICS. Sending
1821    without a delay causes problems when using timestamp on ICC
1822    (at least on my machine). */
1823 void
1824 SendToICSDelayed(s,msdelay)
1825      char *s;
1826      long msdelay;
1827 {
1828     int count, outCount, outError;
1829
1830     if (icsPR == NULL) return;
1831
1832     count = strlen(s);
1833     if (appData.debugMode) {
1834         fprintf(debugFP, ">ICS: ");
1835         show_bytes(debugFP, s, count);
1836         fprintf(debugFP, "\n");
1837     }
1838     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1839                                       msdelay);
1840     if (outCount < count) {
1841         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1842     }
1843 }
1844
1845
1846 /* Remove all highlighting escape sequences in s
1847    Also deletes any suffix starting with '('
1848    */
1849 char *
1850 StripHighlightAndTitle(s)
1851      char *s;
1852 {
1853     static char retbuf[MSG_SIZ];
1854     char *p = retbuf;
1855
1856     while (*s != NULLCHAR) {
1857         while (*s == '\033') {
1858             while (*s != NULLCHAR && !isalpha(*s)) s++;
1859             if (*s != NULLCHAR) s++;
1860         }
1861         while (*s != NULLCHAR && *s != '\033') {
1862             if (*s == '(' || *s == '[') {
1863                 *p = NULLCHAR;
1864                 return retbuf;
1865             }
1866             *p++ = *s++;
1867         }
1868     }
1869     *p = NULLCHAR;
1870     return retbuf;
1871 }
1872
1873 /* Remove all highlighting escape sequences in s */
1874 char *
1875 StripHighlight(s)
1876      char *s;
1877 {
1878     static char retbuf[MSG_SIZ];
1879     char *p = retbuf;
1880
1881     while (*s != NULLCHAR) {
1882         while (*s == '\033') {
1883             while (*s != NULLCHAR && !isalpha(*s)) s++;
1884             if (*s != NULLCHAR) s++;
1885         }
1886         while (*s != NULLCHAR && *s != '\033') {
1887             *p++ = *s++;
1888         }
1889     }
1890     *p = NULLCHAR;
1891     return retbuf;
1892 }
1893
1894 char *variantNames[] = VARIANT_NAMES;
1895 char *
1896 VariantName(v)
1897      VariantClass v;
1898 {
1899     return variantNames[v];
1900 }
1901
1902
1903 /* Identify a variant from the strings the chess servers use or the
1904    PGN Variant tag names we use. */
1905 VariantClass
1906 StringToVariant(e)
1907      char *e;
1908 {
1909     char *p;
1910     int wnum = -1;
1911     VariantClass v = VariantNormal;
1912     int i, found = FALSE;
1913     char buf[MSG_SIZ];
1914     int len;
1915
1916     if (!e) return v;
1917
1918     /* [HGM] skip over optional board-size prefixes */
1919     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1920         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1921         while( *e++ != '_');
1922     }
1923
1924     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1925         v = VariantNormal;
1926         found = TRUE;
1927     } else
1928     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1929       if (StrCaseStr(e, variantNames[i])) {
1930         v = (VariantClass) i;
1931         found = TRUE;
1932         break;
1933       }
1934     }
1935
1936     if (!found) {
1937       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1938           || StrCaseStr(e, "wild/fr")
1939           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1940         v = VariantFischeRandom;
1941       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1942                  (i = 1, p = StrCaseStr(e, "w"))) {
1943         p += i;
1944         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1945         if (isdigit(*p)) {
1946           wnum = atoi(p);
1947         } else {
1948           wnum = -1;
1949         }
1950         switch (wnum) {
1951         case 0: /* FICS only, actually */
1952         case 1:
1953           /* Castling legal even if K starts on d-file */
1954           v = VariantWildCastle;
1955           break;
1956         case 2:
1957         case 3:
1958         case 4:
1959           /* Castling illegal even if K & R happen to start in
1960              normal positions. */
1961           v = VariantNoCastle;
1962           break;
1963         case 5:
1964         case 7:
1965         case 8:
1966         case 10:
1967         case 11:
1968         case 12:
1969         case 13:
1970         case 14:
1971         case 15:
1972         case 18:
1973         case 19:
1974           /* Castling legal iff K & R start in normal positions */
1975           v = VariantNormal;
1976           break;
1977         case 6:
1978         case 20:
1979         case 21:
1980           /* Special wilds for position setup; unclear what to do here */
1981           v = VariantLoadable;
1982           break;
1983         case 9:
1984           /* Bizarre ICC game */
1985           v = VariantTwoKings;
1986           break;
1987         case 16:
1988           v = VariantKriegspiel;
1989           break;
1990         case 17:
1991           v = VariantLosers;
1992           break;
1993         case 22:
1994           v = VariantFischeRandom;
1995           break;
1996         case 23:
1997           v = VariantCrazyhouse;
1998           break;
1999         case 24:
2000           v = VariantBughouse;
2001           break;
2002         case 25:
2003           v = Variant3Check;
2004           break;
2005         case 26:
2006           /* Not quite the same as FICS suicide! */
2007           v = VariantGiveaway;
2008           break;
2009         case 27:
2010           v = VariantAtomic;
2011           break;
2012         case 28:
2013           v = VariantShatranj;
2014           break;
2015
2016         /* Temporary names for future ICC types.  The name *will* change in
2017            the next xboard/WinBoard release after ICC defines it. */
2018         case 29:
2019           v = Variant29;
2020           break;
2021         case 30:
2022           v = Variant30;
2023           break;
2024         case 31:
2025           v = Variant31;
2026           break;
2027         case 32:
2028           v = Variant32;
2029           break;
2030         case 33:
2031           v = Variant33;
2032           break;
2033         case 34:
2034           v = Variant34;
2035           break;
2036         case 35:
2037           v = Variant35;
2038           break;
2039         case 36:
2040           v = Variant36;
2041           break;
2042         case 37:
2043           v = VariantShogi;
2044           break;
2045         case 38:
2046           v = VariantXiangqi;
2047           break;
2048         case 39:
2049           v = VariantCourier;
2050           break;
2051         case 40:
2052           v = VariantGothic;
2053           break;
2054         case 41:
2055           v = VariantCapablanca;
2056           break;
2057         case 42:
2058           v = VariantKnightmate;
2059           break;
2060         case 43:
2061           v = VariantFairy;
2062           break;
2063         case 44:
2064           v = VariantCylinder;
2065           break;
2066         case 45:
2067           v = VariantFalcon;
2068           break;
2069         case 46:
2070           v = VariantCapaRandom;
2071           break;
2072         case 47:
2073           v = VariantBerolina;
2074           break;
2075         case 48:
2076           v = VariantJanus;
2077           break;
2078         case 49:
2079           v = VariantSuper;
2080           break;
2081         case 50:
2082           v = VariantGreat;
2083           break;
2084         case -1:
2085           /* Found "wild" or "w" in the string but no number;
2086              must assume it's normal chess. */
2087           v = VariantNormal;
2088           break;
2089         default:
2090           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2091           if( (len > MSG_SIZ) && appData.debugMode )
2092             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2093
2094           DisplayError(buf, 0);
2095           v = VariantUnknown;
2096           break;
2097         }
2098       }
2099     }
2100     if (appData.debugMode) {
2101       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2102               e, wnum, VariantName(v));
2103     }
2104     return v;
2105 }
2106
2107 static int leftover_start = 0, leftover_len = 0;
2108 char star_match[STAR_MATCH_N][MSG_SIZ];
2109
2110 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2111    advance *index beyond it, and set leftover_start to the new value of
2112    *index; else return FALSE.  If pattern contains the character '*', it
2113    matches any sequence of characters not containing '\r', '\n', or the
2114    character following the '*' (if any), and the matched sequence(s) are
2115    copied into star_match.
2116    */
2117 int
2118 looking_at(buf, index, pattern)
2119      char *buf;
2120      int *index;
2121      char *pattern;
2122 {
2123     char *bufp = &buf[*index], *patternp = pattern;
2124     int star_count = 0;
2125     char *matchp = star_match[0];
2126
2127     for (;;) {
2128         if (*patternp == NULLCHAR) {
2129             *index = leftover_start = bufp - buf;
2130             *matchp = NULLCHAR;
2131             return TRUE;
2132         }
2133         if (*bufp == NULLCHAR) return FALSE;
2134         if (*patternp == '*') {
2135             if (*bufp == *(patternp + 1)) {
2136                 *matchp = NULLCHAR;
2137                 matchp = star_match[++star_count];
2138                 patternp += 2;
2139                 bufp++;
2140                 continue;
2141             } else if (*bufp == '\n' || *bufp == '\r') {
2142                 patternp++;
2143                 if (*patternp == NULLCHAR)
2144                   continue;
2145                 else
2146                   return FALSE;
2147             } else {
2148                 *matchp++ = *bufp++;
2149                 continue;
2150             }
2151         }
2152         if (*patternp != *bufp) return FALSE;
2153         patternp++;
2154         bufp++;
2155     }
2156 }
2157
2158 void
2159 SendToPlayer(data, length)
2160      char *data;
2161      int length;
2162 {
2163     int error, outCount;
2164     outCount = OutputToProcess(NoProc, data, length, &error);
2165     if (outCount < length) {
2166         DisplayFatalError(_("Error writing to display"), error, 1);
2167     }
2168 }
2169
2170 void
2171 PackHolding(packed, holding)
2172      char packed[];
2173      char *holding;
2174 {
2175     char *p = holding;
2176     char *q = packed;
2177     int runlength = 0;
2178     int curr = 9999;
2179     do {
2180         if (*p == curr) {
2181             runlength++;
2182         } else {
2183             switch (runlength) {
2184               case 0:
2185                 break;
2186               case 1:
2187                 *q++ = curr;
2188                 break;
2189               case 2:
2190                 *q++ = curr;
2191                 *q++ = curr;
2192                 break;
2193               default:
2194                 sprintf(q, "%d", runlength);
2195                 while (*q) q++;
2196                 *q++ = curr;
2197                 break;
2198             }
2199             runlength = 1;
2200             curr = *p;
2201         }
2202     } while (*p++);
2203     *q = NULLCHAR;
2204 }
2205
2206 /* Telnet protocol requests from the front end */
2207 void
2208 TelnetRequest(ddww, option)
2209      unsigned char ddww, option;
2210 {
2211     unsigned char msg[3];
2212     int outCount, outError;
2213
2214     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2215
2216     if (appData.debugMode) {
2217         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2218         switch (ddww) {
2219           case TN_DO:
2220             ddwwStr = "DO";
2221             break;
2222           case TN_DONT:
2223             ddwwStr = "DONT";
2224             break;
2225           case TN_WILL:
2226             ddwwStr = "WILL";
2227             break;
2228           case TN_WONT:
2229             ddwwStr = "WONT";
2230             break;
2231           default:
2232             ddwwStr = buf1;
2233             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2234             break;
2235         }
2236         switch (option) {
2237           case TN_ECHO:
2238             optionStr = "ECHO";
2239             break;
2240           default:
2241             optionStr = buf2;
2242             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2243             break;
2244         }
2245         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2246     }
2247     msg[0] = TN_IAC;
2248     msg[1] = ddww;
2249     msg[2] = option;
2250     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2251     if (outCount < 3) {
2252         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2253     }
2254 }
2255
2256 void
2257 DoEcho()
2258 {
2259     if (!appData.icsActive) return;
2260     TelnetRequest(TN_DO, TN_ECHO);
2261 }
2262
2263 void
2264 DontEcho()
2265 {
2266     if (!appData.icsActive) return;
2267     TelnetRequest(TN_DONT, TN_ECHO);
2268 }
2269
2270 void
2271 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2272 {
2273     /* put the holdings sent to us by the server on the board holdings area */
2274     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2275     char p;
2276     ChessSquare piece;
2277
2278     if(gameInfo.holdingsWidth < 2)  return;
2279     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2280         return; // prevent overwriting by pre-board holdings
2281
2282     if( (int)lowestPiece >= BlackPawn ) {
2283         holdingsColumn = 0;
2284         countsColumn = 1;
2285         holdingsStartRow = BOARD_HEIGHT-1;
2286         direction = -1;
2287     } else {
2288         holdingsColumn = BOARD_WIDTH-1;
2289         countsColumn = BOARD_WIDTH-2;
2290         holdingsStartRow = 0;
2291         direction = 1;
2292     }
2293
2294     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2295         board[i][holdingsColumn] = EmptySquare;
2296         board[i][countsColumn]   = (ChessSquare) 0;
2297     }
2298     while( (p=*holdings++) != NULLCHAR ) {
2299         piece = CharToPiece( ToUpper(p) );
2300         if(piece == EmptySquare) continue;
2301         /*j = (int) piece - (int) WhitePawn;*/
2302         j = PieceToNumber(piece);
2303         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2304         if(j < 0) continue;               /* should not happen */
2305         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2306         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2307         board[holdingsStartRow+j*direction][countsColumn]++;
2308     }
2309 }
2310
2311
2312 void
2313 VariantSwitch(Board board, VariantClass newVariant)
2314 {
2315    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2316    static Board oldBoard;
2317
2318    startedFromPositionFile = FALSE;
2319    if(gameInfo.variant == newVariant) return;
2320
2321    /* [HGM] This routine is called each time an assignment is made to
2322     * gameInfo.variant during a game, to make sure the board sizes
2323     * are set to match the new variant. If that means adding or deleting
2324     * holdings, we shift the playing board accordingly
2325     * This kludge is needed because in ICS observe mode, we get boards
2326     * of an ongoing game without knowing the variant, and learn about the
2327     * latter only later. This can be because of the move list we requested,
2328     * in which case the game history is refilled from the beginning anyway,
2329     * but also when receiving holdings of a crazyhouse game. In the latter
2330     * case we want to add those holdings to the already received position.
2331     */
2332
2333
2334    if (appData.debugMode) {
2335      fprintf(debugFP, "Switch board from %s to %s\n",
2336              VariantName(gameInfo.variant), VariantName(newVariant));
2337      setbuf(debugFP, NULL);
2338    }
2339    shuffleOpenings = 0;       /* [HGM] shuffle */
2340    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2341    switch(newVariant)
2342      {
2343      case VariantShogi:
2344        newWidth = 9;  newHeight = 9;
2345        gameInfo.holdingsSize = 7;
2346      case VariantBughouse:
2347      case VariantCrazyhouse:
2348        newHoldingsWidth = 2; break;
2349      case VariantGreat:
2350        newWidth = 10;
2351      case VariantSuper:
2352        newHoldingsWidth = 2;
2353        gameInfo.holdingsSize = 8;
2354        break;
2355      case VariantGothic:
2356      case VariantCapablanca:
2357      case VariantCapaRandom:
2358        newWidth = 10;
2359      default:
2360        newHoldingsWidth = gameInfo.holdingsSize = 0;
2361      };
2362
2363    if(newWidth  != gameInfo.boardWidth  ||
2364       newHeight != gameInfo.boardHeight ||
2365       newHoldingsWidth != gameInfo.holdingsWidth ) {
2366
2367      /* shift position to new playing area, if needed */
2368      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2369        for(i=0; i<BOARD_HEIGHT; i++)
2370          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2371            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2372              board[i][j];
2373        for(i=0; i<newHeight; i++) {
2374          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2375          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2376        }
2377      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2378        for(i=0; i<BOARD_HEIGHT; i++)
2379          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2380            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2381              board[i][j];
2382      }
2383      gameInfo.boardWidth  = newWidth;
2384      gameInfo.boardHeight = newHeight;
2385      gameInfo.holdingsWidth = newHoldingsWidth;
2386      gameInfo.variant = newVariant;
2387      InitDrawingSizes(-2, 0);
2388    } else gameInfo.variant = newVariant;
2389    CopyBoard(oldBoard, board);   // remember correctly formatted board
2390      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2391    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2392 }
2393
2394 static int loggedOn = FALSE;
2395
2396 /*-- Game start info cache: --*/
2397 int gs_gamenum;
2398 char gs_kind[MSG_SIZ];
2399 static char player1Name[128] = "";
2400 static char player2Name[128] = "";
2401 static char cont_seq[] = "\n\\   ";
2402 static int player1Rating = -1;
2403 static int player2Rating = -1;
2404 /*----------------------------*/
2405
2406 ColorClass curColor = ColorNormal;
2407 int suppressKibitz = 0;
2408
2409 // [HGM] seekgraph
2410 Boolean soughtPending = FALSE;
2411 Boolean seekGraphUp;
2412 #define MAX_SEEK_ADS 200
2413 #define SQUARE 0x80
2414 char *seekAdList[MAX_SEEK_ADS];
2415 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2416 float tcList[MAX_SEEK_ADS];
2417 char colorList[MAX_SEEK_ADS];
2418 int nrOfSeekAds = 0;
2419 int minRating = 1010, maxRating = 2800;
2420 int hMargin = 10, vMargin = 20, h, w;
2421 extern int squareSize, lineGap;
2422
2423 void
2424 PlotSeekAd(int i)
2425 {
2426         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2427         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2428         if(r < minRating+100 && r >=0 ) r = minRating+100;
2429         if(r > maxRating) r = maxRating;
2430         if(tc < 1.) tc = 1.;
2431         if(tc > 95.) tc = 95.;
2432         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2433         y = ((double)r - minRating)/(maxRating - minRating)
2434             * (h-vMargin-squareSize/8-1) + vMargin;
2435         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2436         if(strstr(seekAdList[i], " u ")) color = 1;
2437         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2438            !strstr(seekAdList[i], "bullet") &&
2439            !strstr(seekAdList[i], "blitz") &&
2440            !strstr(seekAdList[i], "standard") ) color = 2;
2441         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2442         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2443 }
2444
2445 void
2446 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2447 {
2448         char buf[MSG_SIZ], *ext = "";
2449         VariantClass v = StringToVariant(type);
2450         if(strstr(type, "wild")) {
2451             ext = type + 4; // append wild number
2452             if(v == VariantFischeRandom) type = "chess960"; else
2453             if(v == VariantLoadable) type = "setup"; else
2454             type = VariantName(v);
2455         }
2456         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2457         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2458             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2459             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2460             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2461             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2462             seekNrList[nrOfSeekAds] = nr;
2463             zList[nrOfSeekAds] = 0;
2464             seekAdList[nrOfSeekAds++] = StrSave(buf);
2465             if(plot) PlotSeekAd(nrOfSeekAds-1);
2466         }
2467 }
2468
2469 void
2470 EraseSeekDot(int i)
2471 {
2472     int x = xList[i], y = yList[i], d=squareSize/4, k;
2473     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2474     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2475     // now replot every dot that overlapped
2476     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2477         int xx = xList[k], yy = yList[k];
2478         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2479             DrawSeekDot(xx, yy, colorList[k]);
2480     }
2481 }
2482
2483 void
2484 RemoveSeekAd(int nr)
2485 {
2486         int i;
2487         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2488             EraseSeekDot(i);
2489             if(seekAdList[i]) free(seekAdList[i]);
2490             seekAdList[i] = seekAdList[--nrOfSeekAds];
2491             seekNrList[i] = seekNrList[nrOfSeekAds];
2492             ratingList[i] = ratingList[nrOfSeekAds];
2493             colorList[i]  = colorList[nrOfSeekAds];
2494             tcList[i] = tcList[nrOfSeekAds];
2495             xList[i]  = xList[nrOfSeekAds];
2496             yList[i]  = yList[nrOfSeekAds];
2497             zList[i]  = zList[nrOfSeekAds];
2498             seekAdList[nrOfSeekAds] = NULL;
2499             break;
2500         }
2501 }
2502
2503 Boolean
2504 MatchSoughtLine(char *line)
2505 {
2506     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2507     int nr, base, inc, u=0; char dummy;
2508
2509     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2510        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2511        (u=1) &&
2512        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2513         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2514         // match: compact and save the line
2515         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2516         return TRUE;
2517     }
2518     return FALSE;
2519 }
2520
2521 int
2522 DrawSeekGraph()
2523 {
2524     int i;
2525     if(!seekGraphUp) return FALSE;
2526     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2527     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2528
2529     DrawSeekBackground(0, 0, w, h);
2530     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2531     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2532     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2533         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2534         yy = h-1-yy;
2535         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2536         if(i%500 == 0) {
2537             char buf[MSG_SIZ];
2538             snprintf(buf, MSG_SIZ, "%d", i);
2539             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2540         }
2541     }
2542     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2543     for(i=1; i<100; i+=(i<10?1:5)) {
2544         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2545         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2546         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2547             char buf[MSG_SIZ];
2548             snprintf(buf, MSG_SIZ, "%d", i);
2549             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2550         }
2551     }
2552     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2553     return TRUE;
2554 }
2555
2556 int SeekGraphClick(ClickType click, int x, int y, int moving)
2557 {
2558     static int lastDown = 0, displayed = 0, lastSecond;
2559     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2560         if(click == Release || moving) return FALSE;
2561         nrOfSeekAds = 0;
2562         soughtPending = TRUE;
2563         SendToICS(ics_prefix);
2564         SendToICS("sought\n"); // should this be "sought all"?
2565     } else { // issue challenge based on clicked ad
2566         int dist = 10000; int i, closest = 0, second = 0;
2567         for(i=0; i<nrOfSeekAds; i++) {
2568             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2569             if(d < dist) { dist = d; closest = i; }
2570             second += (d - zList[i] < 120); // count in-range ads
2571             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2572         }
2573         if(dist < 120) {
2574             char buf[MSG_SIZ];
2575             second = (second > 1);
2576             if(displayed != closest || second != lastSecond) {
2577                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2578                 lastSecond = second; displayed = closest;
2579             }
2580             if(click == Press) {
2581                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2582                 lastDown = closest;
2583                 return TRUE;
2584             } // on press 'hit', only show info
2585             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2586             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2587             SendToICS(ics_prefix);
2588             SendToICS(buf);
2589             return TRUE; // let incoming board of started game pop down the graph
2590         } else if(click == Release) { // release 'miss' is ignored
2591             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2592             if(moving == 2) { // right up-click
2593                 nrOfSeekAds = 0; // refresh graph
2594                 soughtPending = TRUE;
2595                 SendToICS(ics_prefix);
2596                 SendToICS("sought\n"); // should this be "sought all"?
2597             }
2598             return TRUE;
2599         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2600         // press miss or release hit 'pop down' seek graph
2601         seekGraphUp = FALSE;
2602         DrawPosition(TRUE, NULL);
2603     }
2604     return TRUE;
2605 }
2606
2607 void
2608 read_from_ics(isr, closure, data, count, error)
2609      InputSourceRef isr;
2610      VOIDSTAR closure;
2611      char *data;
2612      int count;
2613      int error;
2614 {
2615 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2616 #define STARTED_NONE 0
2617 #define STARTED_MOVES 1
2618 #define STARTED_BOARD 2
2619 #define STARTED_OBSERVE 3
2620 #define STARTED_HOLDINGS 4
2621 #define STARTED_CHATTER 5
2622 #define STARTED_COMMENT 6
2623 #define STARTED_MOVES_NOHIDE 7
2624
2625     static int started = STARTED_NONE;
2626     static char parse[20000];
2627     static int parse_pos = 0;
2628     static char buf[BUF_SIZE + 1];
2629     static int firstTime = TRUE, intfSet = FALSE;
2630     static ColorClass prevColor = ColorNormal;
2631     static int savingComment = FALSE;
2632     static int cmatch = 0; // continuation sequence match
2633     char *bp;
2634     char str[MSG_SIZ];
2635     int i, oldi;
2636     int buf_len;
2637     int next_out;
2638     int tkind;
2639     int backup;    /* [DM] For zippy color lines */
2640     char *p;
2641     char talker[MSG_SIZ]; // [HGM] chat
2642     int channel;
2643
2644     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2645
2646     if (appData.debugMode) {
2647       if (!error) {
2648         fprintf(debugFP, "<ICS: ");
2649         show_bytes(debugFP, data, count);
2650         fprintf(debugFP, "\n");
2651       }
2652     }
2653
2654     if (appData.debugMode) { int f = forwardMostMove;
2655         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2656                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2657                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2658     }
2659     if (count > 0) {
2660         /* If last read ended with a partial line that we couldn't parse,
2661            prepend it to the new read and try again. */
2662         if (leftover_len > 0) {
2663             for (i=0; i<leftover_len; i++)
2664               buf[i] = buf[leftover_start + i];
2665         }
2666
2667     /* copy new characters into the buffer */
2668     bp = buf + leftover_len;
2669     buf_len=leftover_len;
2670     for (i=0; i<count; i++)
2671     {
2672         // ignore these
2673         if (data[i] == '\r')
2674             continue;
2675
2676         // join lines split by ICS?
2677         if (!appData.noJoin)
2678         {
2679             /*
2680                 Joining just consists of finding matches against the
2681                 continuation sequence, and discarding that sequence
2682                 if found instead of copying it.  So, until a match
2683                 fails, there's nothing to do since it might be the
2684                 complete sequence, and thus, something we don't want
2685                 copied.
2686             */
2687             if (data[i] == cont_seq[cmatch])
2688             {
2689                 cmatch++;
2690                 if (cmatch == strlen(cont_seq))
2691                 {
2692                     cmatch = 0; // complete match.  just reset the counter
2693
2694                     /*
2695                         it's possible for the ICS to not include the space
2696                         at the end of the last word, making our [correct]
2697                         join operation fuse two separate words.  the server
2698                         does this when the space occurs at the width setting.
2699                     */
2700                     if (!buf_len || buf[buf_len-1] != ' ')
2701                     {
2702                         *bp++ = ' ';
2703                         buf_len++;
2704                     }
2705                 }
2706                 continue;
2707             }
2708             else if (cmatch)
2709             {
2710                 /*
2711                     match failed, so we have to copy what matched before
2712                     falling through and copying this character.  In reality,
2713                     this will only ever be just the newline character, but
2714                     it doesn't hurt to be precise.
2715                 */
2716                 strncpy(bp, cont_seq, cmatch);
2717                 bp += cmatch;
2718                 buf_len += cmatch;
2719                 cmatch = 0;
2720             }
2721         }
2722
2723         // copy this char
2724         *bp++ = data[i];
2725         buf_len++;
2726     }
2727
2728         buf[buf_len] = NULLCHAR;
2729 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2730         next_out = 0;
2731         leftover_start = 0;
2732
2733         i = 0;
2734         while (i < buf_len) {
2735             /* Deal with part of the TELNET option negotiation
2736                protocol.  We refuse to do anything beyond the
2737                defaults, except that we allow the WILL ECHO option,
2738                which ICS uses to turn off password echoing when we are
2739                directly connected to it.  We reject this option
2740                if localLineEditing mode is on (always on in xboard)
2741                and we are talking to port 23, which might be a real
2742                telnet server that will try to keep WILL ECHO on permanently.
2743              */
2744             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2745                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2746                 unsigned char option;
2747                 oldi = i;
2748                 switch ((unsigned char) buf[++i]) {
2749                   case TN_WILL:
2750                     if (appData.debugMode)
2751                       fprintf(debugFP, "\n<WILL ");
2752                     switch (option = (unsigned char) buf[++i]) {
2753                       case TN_ECHO:
2754                         if (appData.debugMode)
2755                           fprintf(debugFP, "ECHO ");
2756                         /* Reply only if this is a change, according
2757                            to the protocol rules. */
2758                         if (remoteEchoOption) break;
2759                         if (appData.localLineEditing &&
2760                             atoi(appData.icsPort) == TN_PORT) {
2761                             TelnetRequest(TN_DONT, TN_ECHO);
2762                         } else {
2763                             EchoOff();
2764                             TelnetRequest(TN_DO, TN_ECHO);
2765                             remoteEchoOption = TRUE;
2766                         }
2767                         break;
2768                       default:
2769                         if (appData.debugMode)
2770                           fprintf(debugFP, "%d ", option);
2771                         /* Whatever this is, we don't want it. */
2772                         TelnetRequest(TN_DONT, option);
2773                         break;
2774                     }
2775                     break;
2776                   case TN_WONT:
2777                     if (appData.debugMode)
2778                       fprintf(debugFP, "\n<WONT ");
2779                     switch (option = (unsigned char) buf[++i]) {
2780                       case TN_ECHO:
2781                         if (appData.debugMode)
2782                           fprintf(debugFP, "ECHO ");
2783                         /* Reply only if this is a change, according
2784                            to the protocol rules. */
2785                         if (!remoteEchoOption) break;
2786                         EchoOn();
2787                         TelnetRequest(TN_DONT, TN_ECHO);
2788                         remoteEchoOption = FALSE;
2789                         break;
2790                       default:
2791                         if (appData.debugMode)
2792                           fprintf(debugFP, "%d ", (unsigned char) option);
2793                         /* Whatever this is, it must already be turned
2794                            off, because we never agree to turn on
2795                            anything non-default, so according to the
2796                            protocol rules, we don't reply. */
2797                         break;
2798                     }
2799                     break;
2800                   case TN_DO:
2801                     if (appData.debugMode)
2802                       fprintf(debugFP, "\n<DO ");
2803                     switch (option = (unsigned char) buf[++i]) {
2804                       default:
2805                         /* Whatever this is, we refuse to do it. */
2806                         if (appData.debugMode)
2807                           fprintf(debugFP, "%d ", option);
2808                         TelnetRequest(TN_WONT, option);
2809                         break;
2810                     }
2811                     break;
2812                   case TN_DONT:
2813                     if (appData.debugMode)
2814                       fprintf(debugFP, "\n<DONT ");
2815                     switch (option = (unsigned char) buf[++i]) {
2816                       default:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "%d ", option);
2819                         /* Whatever this is, we are already not doing
2820                            it, because we never agree to do anything
2821                            non-default, so according to the protocol
2822                            rules, we don't reply. */
2823                         break;
2824                     }
2825                     break;
2826                   case TN_IAC:
2827                     if (appData.debugMode)
2828                       fprintf(debugFP, "\n<IAC ");
2829                     /* Doubled IAC; pass it through */
2830                     i--;
2831                     break;
2832                   default:
2833                     if (appData.debugMode)
2834                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2835                     /* Drop all other telnet commands on the floor */
2836                     break;
2837                 }
2838                 if (oldi > next_out)
2839                   SendToPlayer(&buf[next_out], oldi - next_out);
2840                 if (++i > next_out)
2841                   next_out = i;
2842                 continue;
2843             }
2844
2845             /* OK, this at least will *usually* work */
2846             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2847                 loggedOn = TRUE;
2848             }
2849
2850             if (loggedOn && !intfSet) {
2851                 if (ics_type == ICS_ICC) {
2852                   snprintf(str, MSG_SIZ,
2853                           "/set-quietly interface %s\n/set-quietly style 12\n",
2854                           programVersion);
2855                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2856                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2857                 } else if (ics_type == ICS_CHESSNET) {
2858                   snprintf(str, MSG_SIZ, "/style 12\n");
2859                 } else {
2860                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2861                   strcat(str, programVersion);
2862                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2863                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2864                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2865 #ifdef WIN32
2866                   strcat(str, "$iset nohighlight 1\n");
2867 #endif
2868                   strcat(str, "$iset lock 1\n$style 12\n");
2869                 }
2870                 SendToICS(str);
2871                 NotifyFrontendLogin();
2872                 intfSet = TRUE;
2873             }
2874
2875             if (started == STARTED_COMMENT) {
2876                 /* Accumulate characters in comment */
2877                 parse[parse_pos++] = buf[i];
2878                 if (buf[i] == '\n') {
2879                     parse[parse_pos] = NULLCHAR;
2880                     if(chattingPartner>=0) {
2881                         char mess[MSG_SIZ];
2882                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2883                         OutputChatMessage(chattingPartner, mess);
2884                         chattingPartner = -1;
2885                         next_out = i+1; // [HGM] suppress printing in ICS window
2886                     } else
2887                     if(!suppressKibitz) // [HGM] kibitz
2888                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2889                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2890                         int nrDigit = 0, nrAlph = 0, j;
2891                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2892                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2893                         parse[parse_pos] = NULLCHAR;
2894                         // try to be smart: if it does not look like search info, it should go to
2895                         // ICS interaction window after all, not to engine-output window.
2896                         for(j=0; j<parse_pos; j++) { // count letters and digits
2897                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2898                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2899                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2900                         }
2901                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2902                             int depth=0; float score;
2903                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2904                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2905                                 pvInfoList[forwardMostMove-1].depth = depth;
2906                                 pvInfoList[forwardMostMove-1].score = 100*score;
2907                             }
2908                             OutputKibitz(suppressKibitz, parse);
2909                         } else {
2910                             char tmp[MSG_SIZ];
2911                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2912                             SendToPlayer(tmp, strlen(tmp));
2913                         }
2914                         next_out = i+1; // [HGM] suppress printing in ICS window
2915                     }
2916                     started = STARTED_NONE;
2917                 } else {
2918                     /* Don't match patterns against characters in comment */
2919                     i++;
2920                     continue;
2921                 }
2922             }
2923             if (started == STARTED_CHATTER) {
2924                 if (buf[i] != '\n') {
2925                     /* Don't match patterns against characters in chatter */
2926                     i++;
2927                     continue;
2928                 }
2929                 started = STARTED_NONE;
2930                 if(suppressKibitz) next_out = i+1;
2931             }
2932
2933             /* Kludge to deal with rcmd protocol */
2934             if (firstTime && looking_at(buf, &i, "\001*")) {
2935                 DisplayFatalError(&buf[1], 0, 1);
2936                 continue;
2937             } else {
2938                 firstTime = FALSE;
2939             }
2940
2941             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2942                 ics_type = ICS_ICC;
2943                 ics_prefix = "/";
2944                 if (appData.debugMode)
2945                   fprintf(debugFP, "ics_type %d\n", ics_type);
2946                 continue;
2947             }
2948             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2949                 ics_type = ICS_FICS;
2950                 ics_prefix = "$";
2951                 if (appData.debugMode)
2952                   fprintf(debugFP, "ics_type %d\n", ics_type);
2953                 continue;
2954             }
2955             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2956                 ics_type = ICS_CHESSNET;
2957                 ics_prefix = "/";
2958                 if (appData.debugMode)
2959                   fprintf(debugFP, "ics_type %d\n", ics_type);
2960                 continue;
2961             }
2962
2963             if (!loggedOn &&
2964                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2965                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2966                  looking_at(buf, &i, "will be \"*\""))) {
2967               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2968               continue;
2969             }
2970
2971             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2972               char buf[MSG_SIZ];
2973               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2974               DisplayIcsInteractionTitle(buf);
2975               have_set_title = TRUE;
2976             }
2977
2978             /* skip finger notes */
2979             if (started == STARTED_NONE &&
2980                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2981                  (buf[i] == '1' && buf[i+1] == '0')) &&
2982                 buf[i+2] == ':' && buf[i+3] == ' ') {
2983               started = STARTED_CHATTER;
2984               i += 3;
2985               continue;
2986             }
2987
2988             oldi = i;
2989             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2990             if(appData.seekGraph) {
2991                 if(soughtPending && MatchSoughtLine(buf+i)) {
2992                     i = strstr(buf+i, "rated") - buf;
2993                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2994                     next_out = leftover_start = i;
2995                     started = STARTED_CHATTER;
2996                     suppressKibitz = TRUE;
2997                     continue;
2998                 }
2999                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3000                         && looking_at(buf, &i, "* ads displayed")) {
3001                     soughtPending = FALSE;
3002                     seekGraphUp = TRUE;
3003                     DrawSeekGraph();
3004                     continue;
3005                 }
3006                 if(appData.autoRefresh) {
3007                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3008                         int s = (ics_type == ICS_ICC); // ICC format differs
3009                         if(seekGraphUp)
3010                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3011                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3012                         looking_at(buf, &i, "*% "); // eat prompt
3013                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3014                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = i; // suppress
3016                         continue;
3017                     }
3018                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3019                         char *p = star_match[0];
3020                         while(*p) {
3021                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3022                             while(*p && *p++ != ' '); // next
3023                         }
3024                         looking_at(buf, &i, "*% "); // eat prompt
3025                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3026                         next_out = i;
3027                         continue;
3028                     }
3029                 }
3030             }
3031
3032             /* skip formula vars */
3033             if (started == STARTED_NONE &&
3034                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3035               started = STARTED_CHATTER;
3036               i += 3;
3037               continue;
3038             }
3039
3040             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3041             if (appData.autoKibitz && started == STARTED_NONE &&
3042                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3043                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3044                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3045                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3046                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3047                         suppressKibitz = TRUE;
3048                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3049                         next_out = i;
3050                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3051                                 && (gameMode == IcsPlayingWhite)) ||
3052                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3053                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3054                             started = STARTED_CHATTER; // own kibitz we simply discard
3055                         else {
3056                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3057                             parse_pos = 0; parse[0] = NULLCHAR;
3058                             savingComment = TRUE;
3059                             suppressKibitz = gameMode != IcsObserving ? 2 :
3060                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3061                         }
3062                         continue;
3063                 } else
3064                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3065                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3066                          && atoi(star_match[0])) {
3067                     // suppress the acknowledgements of our own autoKibitz
3068                     char *p;
3069                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3070                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3071                     SendToPlayer(star_match[0], strlen(star_match[0]));
3072                     if(looking_at(buf, &i, "*% ")) // eat prompt
3073                         suppressKibitz = FALSE;
3074                     next_out = i;
3075                     continue;
3076                 }
3077             } // [HGM] kibitz: end of patch
3078
3079             // [HGM] chat: intercept tells by users for which we have an open chat window
3080             channel = -1;
3081             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3082                                            looking_at(buf, &i, "* whispers:") ||
3083                                            looking_at(buf, &i, "* kibitzes:") ||
3084                                            looking_at(buf, &i, "* shouts:") ||
3085                                            looking_at(buf, &i, "* c-shouts:") ||
3086                                            looking_at(buf, &i, "--> * ") ||
3087                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3088                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3089                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3090                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3091                 int p;
3092                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3093                 chattingPartner = -1;
3094
3095                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3096                 for(p=0; p<MAX_CHAT; p++) {
3097                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3098                     talker[0] = '['; strcat(talker, "] ");
3099                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3100                     chattingPartner = p; break;
3101                     }
3102                 } else
3103                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3104                 for(p=0; p<MAX_CHAT; p++) {
3105                     if(!strcmp("kibitzes", chatPartner[p])) {
3106                         talker[0] = '['; strcat(talker, "] ");
3107                         chattingPartner = p; break;
3108                     }
3109                 } else
3110                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3111                 for(p=0; p<MAX_CHAT; p++) {
3112                     if(!strcmp("whispers", chatPartner[p])) {
3113                         talker[0] = '['; strcat(talker, "] ");
3114                         chattingPartner = p; break;
3115                     }
3116                 } else
3117                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3118                   if(buf[i-8] == '-' && buf[i-3] == 't')
3119                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3120                     if(!strcmp("c-shouts", chatPartner[p])) {
3121                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3122                         chattingPartner = p; break;
3123                     }
3124                   }
3125                   if(chattingPartner < 0)
3126                   for(p=0; p<MAX_CHAT; p++) {
3127                     if(!strcmp("shouts", chatPartner[p])) {
3128                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3129                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3130                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3131                         chattingPartner = p; break;
3132                     }
3133                   }
3134                 }
3135                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3136                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3137                     talker[0] = 0; Colorize(ColorTell, FALSE);
3138                     chattingPartner = p; break;
3139                 }
3140                 if(chattingPartner<0) i = oldi; else {
3141                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3142                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     started = STARTED_COMMENT;
3145                     parse_pos = 0; parse[0] = NULLCHAR;
3146                     savingComment = 3 + chattingPartner; // counts as TRUE
3147                     suppressKibitz = TRUE;
3148                     continue;
3149                 }
3150             } // [HGM] chat: end of patch
3151
3152           backup = i;
3153             if (appData.zippyTalk || appData.zippyPlay) {
3154                 /* [DM] Backup address for color zippy lines */
3155 #if ZIPPY
3156                if (loggedOn == TRUE)
3157                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3158                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3159 #endif
3160             } // [DM] 'else { ' deleted
3161                 if (
3162                     /* Regular tells and says */
3163                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3164                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3165                     looking_at(buf, &i, "* says: ") ||
3166                     /* Don't color "message" or "messages" output */
3167                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3168                     looking_at(buf, &i, "*. * at *:*: ") ||
3169                     looking_at(buf, &i, "--* (*:*): ") ||
3170                     /* Message notifications (same color as tells) */
3171                     looking_at(buf, &i, "* has left a message ") ||
3172                     looking_at(buf, &i, "* just sent you a message:\n") ||
3173                     /* Whispers and kibitzes */
3174                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3175                     looking_at(buf, &i, "* kibitzes: ") ||
3176                     /* Channel tells */
3177                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3178
3179                   if (tkind == 1 && strchr(star_match[0], ':')) {
3180                       /* Avoid "tells you:" spoofs in channels */
3181                      tkind = 3;
3182                   }
3183                   if (star_match[0][0] == NULLCHAR ||
3184                       strchr(star_match[0], ' ') ||
3185                       (tkind == 3 && strchr(star_match[1], ' '))) {
3186                     /* Reject bogus matches */
3187                     i = oldi;
3188                   } else {
3189                     if (appData.colorize) {
3190                       if (oldi > next_out) {
3191                         SendToPlayer(&buf[next_out], oldi - next_out);
3192                         next_out = oldi;
3193                       }
3194                       switch (tkind) {
3195                       case 1:
3196                         Colorize(ColorTell, FALSE);
3197                         curColor = ColorTell;
3198                         break;
3199                       case 2:
3200                         Colorize(ColorKibitz, FALSE);
3201                         curColor = ColorKibitz;
3202                         break;
3203                       case 3:
3204                         p = strrchr(star_match[1], '(');
3205                         if (p == NULL) {
3206                           p = star_match[1];
3207                         } else {
3208                           p++;
3209                         }
3210                         if (atoi(p) == 1) {
3211                           Colorize(ColorChannel1, FALSE);
3212                           curColor = ColorChannel1;
3213                         } else {
3214                           Colorize(ColorChannel, FALSE);
3215                           curColor = ColorChannel;
3216                         }
3217                         break;
3218                       case 5:
3219                         curColor = ColorNormal;
3220                         break;
3221                       }
3222                     }
3223                     if (started == STARTED_NONE && appData.autoComment &&
3224                         (gameMode == IcsObserving ||
3225                          gameMode == IcsPlayingWhite ||
3226                          gameMode == IcsPlayingBlack)) {
3227                       parse_pos = i - oldi;
3228                       memcpy(parse, &buf[oldi], parse_pos);
3229                       parse[parse_pos] = NULLCHAR;
3230                       started = STARTED_COMMENT;
3231                       savingComment = TRUE;
3232                     } else {
3233                       started = STARTED_CHATTER;
3234                       savingComment = FALSE;
3235                     }
3236                     loggedOn = TRUE;
3237                     continue;
3238                   }
3239                 }
3240
3241                 if (looking_at(buf, &i, "* s-shouts: ") ||
3242                     looking_at(buf, &i, "* c-shouts: ")) {
3243                     if (appData.colorize) {
3244                         if (oldi > next_out) {
3245                             SendToPlayer(&buf[next_out], oldi - next_out);
3246                             next_out = oldi;
3247                         }
3248                         Colorize(ColorSShout, FALSE);
3249                         curColor = ColorSShout;
3250                     }
3251                     loggedOn = TRUE;
3252                     started = STARTED_CHATTER;
3253                     continue;
3254                 }
3255
3256                 if (looking_at(buf, &i, "--->")) {
3257                     loggedOn = TRUE;
3258                     continue;
3259                 }
3260
3261                 if (looking_at(buf, &i, "* shouts: ") ||
3262                     looking_at(buf, &i, "--> ")) {
3263                     if (appData.colorize) {
3264                         if (oldi > next_out) {
3265                             SendToPlayer(&buf[next_out], oldi - next_out);
3266                             next_out = oldi;
3267                         }
3268                         Colorize(ColorShout, FALSE);
3269                         curColor = ColorShout;
3270                     }
3271                     loggedOn = TRUE;
3272                     started = STARTED_CHATTER;
3273                     continue;
3274                 }
3275
3276                 if (looking_at( buf, &i, "Challenge:")) {
3277                     if (appData.colorize) {
3278                         if (oldi > next_out) {
3279                             SendToPlayer(&buf[next_out], oldi - next_out);
3280                             next_out = oldi;
3281                         }
3282                         Colorize(ColorChallenge, FALSE);
3283                         curColor = ColorChallenge;
3284                     }
3285                     loggedOn = TRUE;
3286                     continue;
3287                 }
3288
3289                 if (looking_at(buf, &i, "* offers you") ||
3290                     looking_at(buf, &i, "* offers to be") ||
3291                     looking_at(buf, &i, "* would like to") ||
3292                     looking_at(buf, &i, "* requests to") ||
3293                     looking_at(buf, &i, "Your opponent offers") ||
3294                     looking_at(buf, &i, "Your opponent requests")) {
3295
3296                     if (appData.colorize) {
3297                         if (oldi > next_out) {
3298                             SendToPlayer(&buf[next_out], oldi - next_out);
3299                             next_out = oldi;
3300                         }
3301                         Colorize(ColorRequest, FALSE);
3302                         curColor = ColorRequest;
3303                     }
3304                     continue;
3305                 }
3306
3307                 if (looking_at(buf, &i, "* (*) seeking")) {
3308                     if (appData.colorize) {
3309                         if (oldi > next_out) {
3310                             SendToPlayer(&buf[next_out], oldi - next_out);
3311                             next_out = oldi;
3312                         }
3313                         Colorize(ColorSeek, FALSE);
3314                         curColor = ColorSeek;
3315                     }
3316                     continue;
3317             }
3318
3319           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3320
3321             if (looking_at(buf, &i, "\\   ")) {
3322                 if (prevColor != ColorNormal) {
3323                     if (oldi > next_out) {
3324                         SendToPlayer(&buf[next_out], oldi - next_out);
3325                         next_out = oldi;
3326                     }
3327                     Colorize(prevColor, TRUE);
3328                     curColor = prevColor;
3329                 }
3330                 if (savingComment) {
3331                     parse_pos = i - oldi;
3332                     memcpy(parse, &buf[oldi], parse_pos);
3333                     parse[parse_pos] = NULLCHAR;
3334                     started = STARTED_COMMENT;
3335                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3336                         chattingPartner = savingComment - 3; // kludge to remember the box
3337                 } else {
3338                     started = STARTED_CHATTER;
3339                 }
3340                 continue;
3341             }
3342
3343             if (looking_at(buf, &i, "Black Strength :") ||
3344                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3345                 looking_at(buf, &i, "<10>") ||
3346                 looking_at(buf, &i, "#@#")) {
3347                 /* Wrong board style */
3348                 loggedOn = TRUE;
3349                 SendToICS(ics_prefix);
3350                 SendToICS("set style 12\n");
3351                 SendToICS(ics_prefix);
3352                 SendToICS("refresh\n");
3353                 continue;
3354             }
3355
3356             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3357                 ICSInitScript();
3358                 have_sent_ICS_logon = 1;
3359                 continue;
3360             }
3361
3362             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3363                 (looking_at(buf, &i, "\n<12> ") ||
3364                  looking_at(buf, &i, "<12> "))) {
3365                 loggedOn = TRUE;
3366                 if (oldi > next_out) {
3367                     SendToPlayer(&buf[next_out], oldi - next_out);
3368                 }
3369                 next_out = i;
3370                 started = STARTED_BOARD;
3371                 parse_pos = 0;
3372                 continue;
3373             }
3374
3375             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3376                 looking_at(buf, &i, "<b1> ")) {
3377                 if (oldi > next_out) {
3378                     SendToPlayer(&buf[next_out], oldi - next_out);
3379                 }
3380                 next_out = i;
3381                 started = STARTED_HOLDINGS;
3382                 parse_pos = 0;
3383                 continue;
3384             }
3385
3386             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3387                 loggedOn = TRUE;
3388                 /* Header for a move list -- first line */
3389
3390                 switch (ics_getting_history) {
3391                   case H_FALSE:
3392                     switch (gameMode) {
3393                       case IcsIdle:
3394                       case BeginningOfGame:
3395                         /* User typed "moves" or "oldmoves" while we
3396                            were idle.  Pretend we asked for these
3397                            moves and soak them up so user can step
3398                            through them and/or save them.
3399                            */
3400                         Reset(FALSE, TRUE);
3401                         gameMode = IcsObserving;
3402                         ModeHighlight();
3403                         ics_gamenum = -1;
3404                         ics_getting_history = H_GOT_UNREQ_HEADER;
3405                         break;
3406                       case EditGame: /*?*/
3407                       case EditPosition: /*?*/
3408                         /* Should above feature work in these modes too? */
3409                         /* For now it doesn't */
3410                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3411                         break;
3412                       default: