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