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