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