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