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