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