7b5d2fc0a6d38dce474276a96d949e166d764cc3
[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,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     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1324       initialMode = TwoMachinesPlay;
1325     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1326       initialMode = AnalyzeFile;
1327     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1328       initialMode = AnalyzeMode;
1329     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1330       initialMode = MachinePlaysWhite;
1331     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1332       initialMode = MachinePlaysBlack;
1333     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1334       initialMode = EditGame;
1335     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1336       initialMode = EditPosition;
1337     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1338       initialMode = Training;
1339     } else {
1340       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1341       if( (len > MSG_SIZ) && appData.debugMode )
1342         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1343
1344       DisplayFatalError(buf, 0, 2);
1345       return;
1346     }
1347
1348     if (appData.matchMode) {
1349         MatchEvent(TRUE);
1350     } else if (*appData.cmailGameName != NULLCHAR) {
1351         /* Set up cmail mode */
1352         ReloadCmailMsgEvent(TRUE);
1353     } else {
1354         /* Set up other modes */
1355         if (initialMode == AnalyzeFile) {
1356           if (*appData.loadGameFile == NULLCHAR) {
1357             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1358             return;
1359           }
1360         }
1361         if (*appData.loadGameFile != NULLCHAR) {
1362             (void) LoadGameFromFile(appData.loadGameFile,
1363                                     appData.loadGameIndex,
1364                                     appData.loadGameFile, TRUE);
1365         } else if (*appData.loadPositionFile != NULLCHAR) {
1366             (void) LoadPositionFromFile(appData.loadPositionFile,
1367                                         appData.loadPositionIndex,
1368                                         appData.loadPositionFile);
1369             /* [HGM] try to make self-starting even after FEN load */
1370             /* to allow automatic setup of fairy variants with wtm */
1371             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1372                 gameMode = BeginningOfGame;
1373                 setboardSpoiledMachineBlack = 1;
1374             }
1375             /* [HGM] loadPos: make that every new game uses the setup */
1376             /* from file as long as we do not switch variant          */
1377             if(!blackPlaysFirst) {
1378                 startedFromPositionFile = TRUE;
1379                 CopyBoard(filePosition, boards[0]);
1380             }
1381         }
1382         if (initialMode == AnalyzeMode) {
1383           if (appData.noChessProgram) {
1384             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1385             return;
1386           }
1387           if (appData.icsActive) {
1388             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1389             return;
1390           }
1391           AnalyzeModeEvent();
1392         } else if (initialMode == AnalyzeFile) {
1393           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1394           ShowThinkingEvent();
1395           AnalyzeFileEvent();
1396           AnalysisPeriodicEvent(1);
1397         } else if (initialMode == MachinePlaysWhite) {
1398           if (appData.noChessProgram) {
1399             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1400                               0, 2);
1401             return;
1402           }
1403           if (appData.icsActive) {
1404             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1405                               0, 2);
1406             return;
1407           }
1408           MachineWhiteEvent();
1409         } else if (initialMode == MachinePlaysBlack) {
1410           if (appData.noChessProgram) {
1411             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1412                               0, 2);
1413             return;
1414           }
1415           if (appData.icsActive) {
1416             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1417                               0, 2);
1418             return;
1419           }
1420           MachineBlackEvent();
1421         } else if (initialMode == TwoMachinesPlay) {
1422           if (appData.noChessProgram) {
1423             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1424                               0, 2);
1425             return;
1426           }
1427           if (appData.icsActive) {
1428             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1429                               0, 2);
1430             return;
1431           }
1432           TwoMachinesEvent();
1433         } else if (initialMode == EditGame) {
1434           EditGameEvent();
1435         } else if (initialMode == EditPosition) {
1436           EditPositionEvent();
1437         } else if (initialMode == Training) {
1438           if (*appData.loadGameFile == NULLCHAR) {
1439             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1440             return;
1441           }
1442           TrainingEvent();
1443         }
1444     }
1445 }
1446
1447 /*
1448  * Establish will establish a contact to a remote host.port.
1449  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1450  *  used to talk to the host.
1451  * Returns 0 if okay, error code if not.
1452  */
1453 int
1454 establish()
1455 {
1456     char buf[MSG_SIZ];
1457
1458     if (*appData.icsCommPort != NULLCHAR) {
1459         /* Talk to the host through a serial comm port */
1460         return OpenCommPort(appData.icsCommPort, &icsPR);
1461
1462     } else if (*appData.gateway != NULLCHAR) {
1463         if (*appData.remoteShell == NULLCHAR) {
1464             /* Use the rcmd protocol to run telnet program on a gateway host */
1465             snprintf(buf, sizeof(buf), "%s %s %s",
1466                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1467             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1468
1469         } else {
1470             /* Use the rsh program to run telnet program on a gateway host */
1471             if (*appData.remoteUser == NULLCHAR) {
1472                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1473                         appData.gateway, appData.telnetProgram,
1474                         appData.icsHost, appData.icsPort);
1475             } else {
1476                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1477                         appData.remoteShell, appData.gateway,
1478                         appData.remoteUser, appData.telnetProgram,
1479                         appData.icsHost, appData.icsPort);
1480             }
1481             return StartChildProcess(buf, "", &icsPR);
1482
1483         }
1484     } else if (appData.useTelnet) {
1485         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1486
1487     } else {
1488         /* TCP socket interface differs somewhat between
1489            Unix and NT; handle details in the front end.
1490            */
1491         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1492     }
1493 }
1494
1495 void EscapeExpand(char *p, char *q)
1496 {       // [HGM] initstring: routine to shape up string arguments
1497         while(*p++ = *q++) if(p[-1] == '\\')
1498             switch(*q++) {
1499                 case 'n': p[-1] = '\n'; break;
1500                 case 'r': p[-1] = '\r'; break;
1501                 case 't': p[-1] = '\t'; break;
1502                 case '\\': p[-1] = '\\'; break;
1503                 case 0: *p = 0; return;
1504                 default: p[-1] = q[-1]; break;
1505             }
1506 }
1507
1508 void
1509 show_bytes(fp, buf, count)
1510      FILE *fp;
1511      char *buf;
1512      int count;
1513 {
1514     while (count--) {
1515         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1516             fprintf(fp, "\\%03o", *buf & 0xff);
1517         } else {
1518             putc(*buf, fp);
1519         }
1520         buf++;
1521     }
1522     fflush(fp);
1523 }
1524
1525 /* Returns an errno value */
1526 int
1527 OutputMaybeTelnet(pr, message, count, outError)
1528      ProcRef pr;
1529      char *message;
1530      int count;
1531      int *outError;
1532 {
1533     char buf[8192], *p, *q, *buflim;
1534     int left, newcount, outcount;
1535
1536     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1537         *appData.gateway != NULLCHAR) {
1538         if (appData.debugMode) {
1539             fprintf(debugFP, ">ICS: ");
1540             show_bytes(debugFP, message, count);
1541             fprintf(debugFP, "\n");
1542         }
1543         return OutputToProcess(pr, message, count, outError);
1544     }
1545
1546     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1547     p = message;
1548     q = buf;
1549     left = count;
1550     newcount = 0;
1551     while (left) {
1552         if (q >= buflim) {
1553             if (appData.debugMode) {
1554                 fprintf(debugFP, ">ICS: ");
1555                 show_bytes(debugFP, buf, newcount);
1556                 fprintf(debugFP, "\n");
1557             }
1558             outcount = OutputToProcess(pr, buf, newcount, outError);
1559             if (outcount < newcount) return -1; /* to be sure */
1560             q = buf;
1561             newcount = 0;
1562         }
1563         if (*p == '\n') {
1564             *q++ = '\r';
1565             newcount++;
1566         } else if (((unsigned char) *p) == TN_IAC) {
1567             *q++ = (char) TN_IAC;
1568             newcount ++;
1569         }
1570         *q++ = *p++;
1571         newcount++;
1572         left--;
1573     }
1574     if (appData.debugMode) {
1575         fprintf(debugFP, ">ICS: ");
1576         show_bytes(debugFP, buf, newcount);
1577         fprintf(debugFP, "\n");
1578     }
1579     outcount = OutputToProcess(pr, buf, newcount, outError);
1580     if (outcount < newcount) return -1; /* to be sure */
1581     return count;
1582 }
1583
1584 void
1585 read_from_player(isr, closure, message, count, error)
1586      InputSourceRef isr;
1587      VOIDSTAR closure;
1588      char *message;
1589      int count;
1590      int error;
1591 {
1592     int outError, outCount;
1593     static int gotEof = 0;
1594
1595     /* Pass data read from player on to ICS */
1596     if (count > 0) {
1597         gotEof = 0;
1598         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1599         if (outCount < count) {
1600             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1601         }
1602     } else if (count < 0) {
1603         RemoveInputSource(isr);
1604         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1605     } else if (gotEof++ > 0) {
1606         RemoveInputSource(isr);
1607         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1608     }
1609 }
1610
1611 void
1612 KeepAlive()
1613 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1614     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1615     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1616     SendToICS("date\n");
1617     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1618 }
1619
1620 /* added routine for printf style output to ics */
1621 void ics_printf(char *format, ...)
1622 {
1623     char buffer[MSG_SIZ];
1624     va_list args;
1625
1626     va_start(args, format);
1627     vsnprintf(buffer, sizeof(buffer), format, args);
1628     buffer[sizeof(buffer)-1] = '\0';
1629     SendToICS(buffer);
1630     va_end(args);
1631 }
1632
1633 void
1634 SendToICS(s)
1635      char *s;
1636 {
1637     int count, outCount, outError;
1638
1639     if (icsPR == NULL) return;
1640
1641     count = strlen(s);
1642     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1643     if (outCount < count) {
1644         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1645     }
1646 }
1647
1648 /* This is used for sending logon scripts to the ICS. Sending
1649    without a delay causes problems when using timestamp on ICC
1650    (at least on my machine). */
1651 void
1652 SendToICSDelayed(s,msdelay)
1653      char *s;
1654      long msdelay;
1655 {
1656     int count, outCount, outError;
1657
1658     if (icsPR == NULL) return;
1659
1660     count = strlen(s);
1661     if (appData.debugMode) {
1662         fprintf(debugFP, ">ICS: ");
1663         show_bytes(debugFP, s, count);
1664         fprintf(debugFP, "\n");
1665     }
1666     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1667                                       msdelay);
1668     if (outCount < count) {
1669         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1670     }
1671 }
1672
1673
1674 /* Remove all highlighting escape sequences in s
1675    Also deletes any suffix starting with '('
1676    */
1677 char *
1678 StripHighlightAndTitle(s)
1679      char *s;
1680 {
1681     static char retbuf[MSG_SIZ];
1682     char *p = retbuf;
1683
1684     while (*s != NULLCHAR) {
1685         while (*s == '\033') {
1686             while (*s != NULLCHAR && !isalpha(*s)) s++;
1687             if (*s != NULLCHAR) s++;
1688         }
1689         while (*s != NULLCHAR && *s != '\033') {
1690             if (*s == '(' || *s == '[') {
1691                 *p = NULLCHAR;
1692                 return retbuf;
1693             }
1694             *p++ = *s++;
1695         }
1696     }
1697     *p = NULLCHAR;
1698     return retbuf;
1699 }
1700
1701 /* Remove all highlighting escape sequences in s */
1702 char *
1703 StripHighlight(s)
1704      char *s;
1705 {
1706     static char retbuf[MSG_SIZ];
1707     char *p = retbuf;
1708
1709     while (*s != NULLCHAR) {
1710         while (*s == '\033') {
1711             while (*s != NULLCHAR && !isalpha(*s)) s++;
1712             if (*s != NULLCHAR) s++;
1713         }
1714         while (*s != NULLCHAR && *s != '\033') {
1715             *p++ = *s++;
1716         }
1717     }
1718     *p = NULLCHAR;
1719     return retbuf;
1720 }
1721
1722 char *variantNames[] = VARIANT_NAMES;
1723 char *
1724 VariantName(v)
1725      VariantClass v;
1726 {
1727     return variantNames[v];
1728 }
1729
1730
1731 /* Identify a variant from the strings the chess servers use or the
1732    PGN Variant tag names we use. */
1733 VariantClass
1734 StringToVariant(e)
1735      char *e;
1736 {
1737     char *p;
1738     int wnum = -1;
1739     VariantClass v = VariantNormal;
1740     int i, found = FALSE;
1741     char buf[MSG_SIZ];
1742     int len;
1743
1744     if (!e) return v;
1745
1746     /* [HGM] skip over optional board-size prefixes */
1747     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1748         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1749         while( *e++ != '_');
1750     }
1751
1752     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1753         v = VariantNormal;
1754         found = TRUE;
1755     } else
1756     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1757       if (StrCaseStr(e, variantNames[i])) {
1758         v = (VariantClass) i;
1759         found = TRUE;
1760         break;
1761       }
1762     }
1763
1764     if (!found) {
1765       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1766           || StrCaseStr(e, "wild/fr")
1767           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1768         v = VariantFischeRandom;
1769       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1770                  (i = 1, p = StrCaseStr(e, "w"))) {
1771         p += i;
1772         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1773         if (isdigit(*p)) {
1774           wnum = atoi(p);
1775         } else {
1776           wnum = -1;
1777         }
1778         switch (wnum) {
1779         case 0: /* FICS only, actually */
1780         case 1:
1781           /* Castling legal even if K starts on d-file */
1782           v = VariantWildCastle;
1783           break;
1784         case 2:
1785         case 3:
1786         case 4:
1787           /* Castling illegal even if K & R happen to start in
1788              normal positions. */
1789           v = VariantNoCastle;
1790           break;
1791         case 5:
1792         case 7:
1793         case 8:
1794         case 10:
1795         case 11:
1796         case 12:
1797         case 13:
1798         case 14:
1799         case 15:
1800         case 18:
1801         case 19:
1802           /* Castling legal iff K & R start in normal positions */
1803           v = VariantNormal;
1804           break;
1805         case 6:
1806         case 20:
1807         case 21:
1808           /* Special wilds for position setup; unclear what to do here */
1809           v = VariantLoadable;
1810           break;
1811         case 9:
1812           /* Bizarre ICC game */
1813           v = VariantTwoKings;
1814           break;
1815         case 16:
1816           v = VariantKriegspiel;
1817           break;
1818         case 17:
1819           v = VariantLosers;
1820           break;
1821         case 22:
1822           v = VariantFischeRandom;
1823           break;
1824         case 23:
1825           v = VariantCrazyhouse;
1826           break;
1827         case 24:
1828           v = VariantBughouse;
1829           break;
1830         case 25:
1831           v = Variant3Check;
1832           break;
1833         case 26:
1834           /* Not quite the same as FICS suicide! */
1835           v = VariantGiveaway;
1836           break;
1837         case 27:
1838           v = VariantAtomic;
1839           break;
1840         case 28:
1841           v = VariantShatranj;
1842           break;
1843
1844         /* Temporary names for future ICC types.  The name *will* change in
1845            the next xboard/WinBoard release after ICC defines it. */
1846         case 29:
1847           v = Variant29;
1848           break;
1849         case 30:
1850           v = Variant30;
1851           break;
1852         case 31:
1853           v = Variant31;
1854           break;
1855         case 32:
1856           v = Variant32;
1857           break;
1858         case 33:
1859           v = Variant33;
1860           break;
1861         case 34:
1862           v = Variant34;
1863           break;
1864         case 35:
1865           v = Variant35;
1866           break;
1867         case 36:
1868           v = Variant36;
1869           break;
1870         case 37:
1871           v = VariantShogi;
1872           break;
1873         case 38:
1874           v = VariantXiangqi;
1875           break;
1876         case 39:
1877           v = VariantCourier;
1878           break;
1879         case 40:
1880           v = VariantGothic;
1881           break;
1882         case 41:
1883           v = VariantCapablanca;
1884           break;
1885         case 42:
1886           v = VariantKnightmate;
1887           break;
1888         case 43:
1889           v = VariantFairy;
1890           break;
1891         case 44:
1892           v = VariantCylinder;
1893           break;
1894         case 45:
1895           v = VariantFalcon;
1896           break;
1897         case 46:
1898           v = VariantCapaRandom;
1899           break;
1900         case 47:
1901           v = VariantBerolina;
1902           break;
1903         case 48:
1904           v = VariantJanus;
1905           break;
1906         case 49:
1907           v = VariantSuper;
1908           break;
1909         case 50:
1910           v = VariantGreat;
1911           break;
1912         case -1:
1913           /* Found "wild" or "w" in the string but no number;
1914              must assume it's normal chess. */
1915           v = VariantNormal;
1916           break;
1917         default:
1918           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1919           if( (len > MSG_SIZ) && appData.debugMode )
1920             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1921
1922           DisplayError(buf, 0);
1923           v = VariantUnknown;
1924           break;
1925         }
1926       }
1927     }
1928     if (appData.debugMode) {
1929       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1930               e, wnum, VariantName(v));
1931     }
1932     return v;
1933 }
1934
1935 static int leftover_start = 0, leftover_len = 0;
1936 char star_match[STAR_MATCH_N][MSG_SIZ];
1937
1938 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1939    advance *index beyond it, and set leftover_start to the new value of
1940    *index; else return FALSE.  If pattern contains the character '*', it
1941    matches any sequence of characters not containing '\r', '\n', or the
1942    character following the '*' (if any), and the matched sequence(s) are
1943    copied into star_match.
1944    */
1945 int
1946 looking_at(buf, index, pattern)
1947      char *buf;
1948      int *index;
1949      char *pattern;
1950 {
1951     char *bufp = &buf[*index], *patternp = pattern;
1952     int star_count = 0;
1953     char *matchp = star_match[0];
1954
1955     for (;;) {
1956         if (*patternp == NULLCHAR) {
1957             *index = leftover_start = bufp - buf;
1958             *matchp = NULLCHAR;
1959             return TRUE;
1960         }
1961         if (*bufp == NULLCHAR) return FALSE;
1962         if (*patternp == '*') {
1963             if (*bufp == *(patternp + 1)) {
1964                 *matchp = NULLCHAR;
1965                 matchp = star_match[++star_count];
1966                 patternp += 2;
1967                 bufp++;
1968                 continue;
1969             } else if (*bufp == '\n' || *bufp == '\r') {
1970                 patternp++;
1971                 if (*patternp == NULLCHAR)
1972                   continue;
1973                 else
1974                   return FALSE;
1975             } else {
1976                 *matchp++ = *bufp++;
1977                 continue;
1978             }
1979         }
1980         if (*patternp != *bufp) return FALSE;
1981         patternp++;
1982         bufp++;
1983     }
1984 }
1985
1986 void
1987 SendToPlayer(data, length)
1988      char *data;
1989      int length;
1990 {
1991     int error, outCount;
1992     outCount = OutputToProcess(NoProc, data, length, &error);
1993     if (outCount < length) {
1994         DisplayFatalError(_("Error writing to display"), error, 1);
1995     }
1996 }
1997
1998 void
1999 PackHolding(packed, holding)
2000      char packed[];
2001      char *holding;
2002 {
2003     char *p = holding;
2004     char *q = packed;
2005     int runlength = 0;
2006     int curr = 9999;
2007     do {
2008         if (*p == curr) {
2009             runlength++;
2010         } else {
2011             switch (runlength) {
2012               case 0:
2013                 break;
2014               case 1:
2015                 *q++ = curr;
2016                 break;
2017               case 2:
2018                 *q++ = curr;
2019                 *q++ = curr;
2020                 break;
2021               default:
2022                 sprintf(q, "%d", runlength);
2023                 while (*q) q++;
2024                 *q++ = curr;
2025                 break;
2026             }
2027             runlength = 1;
2028             curr = *p;
2029         }
2030     } while (*p++);
2031     *q = NULLCHAR;
2032 }
2033
2034 /* Telnet protocol requests from the front end */
2035 void
2036 TelnetRequest(ddww, option)
2037      unsigned char ddww, option;
2038 {
2039     unsigned char msg[3];
2040     int outCount, outError;
2041
2042     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2043
2044     if (appData.debugMode) {
2045         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2046         switch (ddww) {
2047           case TN_DO:
2048             ddwwStr = "DO";
2049             break;
2050           case TN_DONT:
2051             ddwwStr = "DONT";
2052             break;
2053           case TN_WILL:
2054             ddwwStr = "WILL";
2055             break;
2056           case TN_WONT:
2057             ddwwStr = "WONT";
2058             break;
2059           default:
2060             ddwwStr = buf1;
2061             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2062             break;
2063         }
2064         switch (option) {
2065           case TN_ECHO:
2066             optionStr = "ECHO";
2067             break;
2068           default:
2069             optionStr = buf2;
2070             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2071             break;
2072         }
2073         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2074     }
2075     msg[0] = TN_IAC;
2076     msg[1] = ddww;
2077     msg[2] = option;
2078     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2079     if (outCount < 3) {
2080         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2081     }
2082 }
2083
2084 void
2085 DoEcho()
2086 {
2087     if (!appData.icsActive) return;
2088     TelnetRequest(TN_DO, TN_ECHO);
2089 }
2090
2091 void
2092 DontEcho()
2093 {
2094     if (!appData.icsActive) return;
2095     TelnetRequest(TN_DONT, TN_ECHO);
2096 }
2097
2098 void
2099 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2100 {
2101     /* put the holdings sent to us by the server on the board holdings area */
2102     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2103     char p;
2104     ChessSquare piece;
2105
2106     if(gameInfo.holdingsWidth < 2)  return;
2107     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2108         return; // prevent overwriting by pre-board holdings
2109
2110     if( (int)lowestPiece >= BlackPawn ) {
2111         holdingsColumn = 0;
2112         countsColumn = 1;
2113         holdingsStartRow = BOARD_HEIGHT-1;
2114         direction = -1;
2115     } else {
2116         holdingsColumn = BOARD_WIDTH-1;
2117         countsColumn = BOARD_WIDTH-2;
2118         holdingsStartRow = 0;
2119         direction = 1;
2120     }
2121
2122     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2123         board[i][holdingsColumn] = EmptySquare;
2124         board[i][countsColumn]   = (ChessSquare) 0;
2125     }
2126     while( (p=*holdings++) != NULLCHAR ) {
2127         piece = CharToPiece( ToUpper(p) );
2128         if(piece == EmptySquare) continue;
2129         /*j = (int) piece - (int) WhitePawn;*/
2130         j = PieceToNumber(piece);
2131         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2132         if(j < 0) continue;               /* should not happen */
2133         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2134         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2135         board[holdingsStartRow+j*direction][countsColumn]++;
2136     }
2137 }
2138
2139
2140 void
2141 VariantSwitch(Board board, VariantClass newVariant)
2142 {
2143    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2144    static Board oldBoard;
2145
2146    startedFromPositionFile = FALSE;
2147    if(gameInfo.variant == newVariant) return;
2148
2149    /* [HGM] This routine is called each time an assignment is made to
2150     * gameInfo.variant during a game, to make sure the board sizes
2151     * are set to match the new variant. If that means adding or deleting
2152     * holdings, we shift the playing board accordingly
2153     * This kludge is needed because in ICS observe mode, we get boards
2154     * of an ongoing game without knowing the variant, and learn about the
2155     * latter only later. This can be because of the move list we requested,
2156     * in which case the game history is refilled from the beginning anyway,
2157     * but also when receiving holdings of a crazyhouse game. In the latter
2158     * case we want to add those holdings to the already received position.
2159     */
2160
2161
2162    if (appData.debugMode) {
2163      fprintf(debugFP, "Switch board from %s to %s\n",
2164              VariantName(gameInfo.variant), VariantName(newVariant));
2165      setbuf(debugFP, NULL);
2166    }
2167    shuffleOpenings = 0;       /* [HGM] shuffle */
2168    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2169    switch(newVariant)
2170      {
2171      case VariantShogi:
2172        newWidth = 9;  newHeight = 9;
2173        gameInfo.holdingsSize = 7;
2174      case VariantBughouse:
2175      case VariantCrazyhouse:
2176        newHoldingsWidth = 2; break;
2177      case VariantGreat:
2178        newWidth = 10;
2179      case VariantSuper:
2180        newHoldingsWidth = 2;
2181        gameInfo.holdingsSize = 8;
2182        break;
2183      case VariantGothic:
2184      case VariantCapablanca:
2185      case VariantCapaRandom:
2186        newWidth = 10;
2187      default:
2188        newHoldingsWidth = gameInfo.holdingsSize = 0;
2189      };
2190
2191    if(newWidth  != gameInfo.boardWidth  ||
2192       newHeight != gameInfo.boardHeight ||
2193       newHoldingsWidth != gameInfo.holdingsWidth ) {
2194
2195      /* shift position to new playing area, if needed */
2196      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2197        for(i=0; i<BOARD_HEIGHT; i++)
2198          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2199            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2200              board[i][j];
2201        for(i=0; i<newHeight; i++) {
2202          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2203          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2204        }
2205      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2206        for(i=0; i<BOARD_HEIGHT; i++)
2207          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2208            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2209              board[i][j];
2210      }
2211      gameInfo.boardWidth  = newWidth;
2212      gameInfo.boardHeight = newHeight;
2213      gameInfo.holdingsWidth = newHoldingsWidth;
2214      gameInfo.variant = newVariant;
2215      InitDrawingSizes(-2, 0);
2216    } else gameInfo.variant = newVariant;
2217    CopyBoard(oldBoard, board);   // remember correctly formatted board
2218      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2219    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2220 }
2221
2222 static int loggedOn = FALSE;
2223
2224 /*-- Game start info cache: --*/
2225 int gs_gamenum;
2226 char gs_kind[MSG_SIZ];
2227 static char player1Name[128] = "";
2228 static char player2Name[128] = "";
2229 static char cont_seq[] = "\n\\   ";
2230 static int player1Rating = -1;
2231 static int player2Rating = -1;
2232 /*----------------------------*/
2233
2234 ColorClass curColor = ColorNormal;
2235 int suppressKibitz = 0;
2236
2237 // [HGM] seekgraph
2238 Boolean soughtPending = FALSE;
2239 Boolean seekGraphUp;
2240 #define MAX_SEEK_ADS 200
2241 #define SQUARE 0x80
2242 char *seekAdList[MAX_SEEK_ADS];
2243 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2244 float tcList[MAX_SEEK_ADS];
2245 char colorList[MAX_SEEK_ADS];
2246 int nrOfSeekAds = 0;
2247 int minRating = 1010, maxRating = 2800;
2248 int hMargin = 10, vMargin = 20, h, w;
2249 extern int squareSize, lineGap;
2250
2251 void
2252 PlotSeekAd(int i)
2253 {
2254         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2255         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2256         if(r < minRating+100 && r >=0 ) r = minRating+100;
2257         if(r > maxRating) r = maxRating;
2258         if(tc < 1.) tc = 1.;
2259         if(tc > 95.) tc = 95.;
2260         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2261         y = ((double)r - minRating)/(maxRating - minRating)
2262             * (h-vMargin-squareSize/8-1) + vMargin;
2263         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2264         if(strstr(seekAdList[i], " u ")) color = 1;
2265         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2266            !strstr(seekAdList[i], "bullet") &&
2267            !strstr(seekAdList[i], "blitz") &&
2268            !strstr(seekAdList[i], "standard") ) color = 2;
2269         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2270         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2271 }
2272
2273 void
2274 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2275 {
2276         char buf[MSG_SIZ], *ext = "";
2277         VariantClass v = StringToVariant(type);
2278         if(strstr(type, "wild")) {
2279             ext = type + 4; // append wild number
2280             if(v == VariantFischeRandom) type = "chess960"; else
2281             if(v == VariantLoadable) type = "setup"; else
2282             type = VariantName(v);
2283         }
2284         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2285         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2286             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2287             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2288             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2289             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2290             seekNrList[nrOfSeekAds] = nr;
2291             zList[nrOfSeekAds] = 0;
2292             seekAdList[nrOfSeekAds++] = StrSave(buf);
2293             if(plot) PlotSeekAd(nrOfSeekAds-1);
2294         }
2295 }
2296
2297 void
2298 EraseSeekDot(int i)
2299 {
2300     int x = xList[i], y = yList[i], d=squareSize/4, k;
2301     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2302     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2303     // now replot every dot that overlapped
2304     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2305         int xx = xList[k], yy = yList[k];
2306         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2307             DrawSeekDot(xx, yy, colorList[k]);
2308     }
2309 }
2310
2311 void
2312 RemoveSeekAd(int nr)
2313 {
2314         int i;
2315         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2316             EraseSeekDot(i);
2317             if(seekAdList[i]) free(seekAdList[i]);
2318             seekAdList[i] = seekAdList[--nrOfSeekAds];
2319             seekNrList[i] = seekNrList[nrOfSeekAds];
2320             ratingList[i] = ratingList[nrOfSeekAds];
2321             colorList[i]  = colorList[nrOfSeekAds];
2322             tcList[i] = tcList[nrOfSeekAds];
2323             xList[i]  = xList[nrOfSeekAds];
2324             yList[i]  = yList[nrOfSeekAds];
2325             zList[i]  = zList[nrOfSeekAds];
2326             seekAdList[nrOfSeekAds] = NULL;
2327             break;
2328         }
2329 }
2330
2331 Boolean
2332 MatchSoughtLine(char *line)
2333 {
2334     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2335     int nr, base, inc, u=0; char dummy;
2336
2337     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2338        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2339        (u=1) &&
2340        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2341         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2342         // match: compact and save the line
2343         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2344         return TRUE;
2345     }
2346     return FALSE;
2347 }
2348
2349 int
2350 DrawSeekGraph()
2351 {
2352     int i;
2353     if(!seekGraphUp) return FALSE;
2354     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2355     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2356
2357     DrawSeekBackground(0, 0, w, h);
2358     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2359     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2360     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2361         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2362         yy = h-1-yy;
2363         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2364         if(i%500 == 0) {
2365             char buf[MSG_SIZ];
2366             snprintf(buf, MSG_SIZ, "%d", i);
2367             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2368         }
2369     }
2370     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2371     for(i=1; i<100; i+=(i<10?1:5)) {
2372         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2373         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2374         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2375             char buf[MSG_SIZ];
2376             snprintf(buf, MSG_SIZ, "%d", i);
2377             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2378         }
2379     }
2380     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2381     return TRUE;
2382 }
2383
2384 int SeekGraphClick(ClickType click, int x, int y, int moving)
2385 {
2386     static int lastDown = 0, displayed = 0, lastSecond;
2387     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2388         if(click == Release || moving) return FALSE;
2389         nrOfSeekAds = 0;
2390         soughtPending = TRUE;
2391         SendToICS(ics_prefix);
2392         SendToICS("sought\n"); // should this be "sought all"?
2393     } else { // issue challenge based on clicked ad
2394         int dist = 10000; int i, closest = 0, second = 0;
2395         for(i=0; i<nrOfSeekAds; i++) {
2396             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2397             if(d < dist) { dist = d; closest = i; }
2398             second += (d - zList[i] < 120); // count in-range ads
2399             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2400         }
2401         if(dist < 120) {
2402             char buf[MSG_SIZ];
2403             second = (second > 1);
2404             if(displayed != closest || second != lastSecond) {
2405                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2406                 lastSecond = second; displayed = closest;
2407             }
2408             if(click == Press) {
2409                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2410                 lastDown = closest;
2411                 return TRUE;
2412             } // on press 'hit', only show info
2413             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2414             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2415             SendToICS(ics_prefix);
2416             SendToICS(buf);
2417             return TRUE; // let incoming board of started game pop down the graph
2418         } else if(click == Release) { // release 'miss' is ignored
2419             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2420             if(moving == 2) { // right up-click
2421                 nrOfSeekAds = 0; // refresh graph
2422                 soughtPending = TRUE;
2423                 SendToICS(ics_prefix);
2424                 SendToICS("sought\n"); // should this be "sought all"?
2425             }
2426             return TRUE;
2427         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2428         // press miss or release hit 'pop down' seek graph
2429         seekGraphUp = FALSE;
2430         DrawPosition(TRUE, NULL);
2431     }
2432     return TRUE;
2433 }
2434
2435 void
2436 read_from_ics(isr, closure, data, count, error)
2437      InputSourceRef isr;
2438      VOIDSTAR closure;
2439      char *data;
2440      int count;
2441      int error;
2442 {
2443 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2444 #define STARTED_NONE 0
2445 #define STARTED_MOVES 1
2446 #define STARTED_BOARD 2
2447 #define STARTED_OBSERVE 3
2448 #define STARTED_HOLDINGS 4
2449 #define STARTED_CHATTER 5
2450 #define STARTED_COMMENT 6
2451 #define STARTED_MOVES_NOHIDE 7
2452
2453     static int started = STARTED_NONE;
2454     static char parse[20000];
2455     static int parse_pos = 0;
2456     static char buf[BUF_SIZE + 1];
2457     static int firstTime = TRUE, intfSet = FALSE;
2458     static ColorClass prevColor = ColorNormal;
2459     static int savingComment = FALSE;
2460     static int cmatch = 0; // continuation sequence match
2461     char *bp;
2462     char str[MSG_SIZ];
2463     int i, oldi;
2464     int buf_len;
2465     int next_out;
2466     int tkind;
2467     int backup;    /* [DM] For zippy color lines */
2468     char *p;
2469     char talker[MSG_SIZ]; // [HGM] chat
2470     int channel;
2471
2472     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2473
2474     if (appData.debugMode) {
2475       if (!error) {
2476         fprintf(debugFP, "<ICS: ");
2477         show_bytes(debugFP, data, count);
2478         fprintf(debugFP, "\n");
2479       }
2480     }
2481
2482     if (appData.debugMode) { int f = forwardMostMove;
2483         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2484                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2485                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2486     }
2487     if (count > 0) {
2488         /* If last read ended with a partial line that we couldn't parse,
2489            prepend it to the new read and try again. */
2490         if (leftover_len > 0) {
2491             for (i=0; i<leftover_len; i++)
2492               buf[i] = buf[leftover_start + i];
2493         }
2494
2495     /* copy new characters into the buffer */
2496     bp = buf + leftover_len;
2497     buf_len=leftover_len;
2498     for (i=0; i<count; i++)
2499     {
2500         // ignore these
2501         if (data[i] == '\r')
2502             continue;
2503
2504         // join lines split by ICS?
2505         if (!appData.noJoin)
2506         {
2507             /*
2508                 Joining just consists of finding matches against the
2509                 continuation sequence, and discarding that sequence
2510                 if found instead of copying it.  So, until a match
2511                 fails, there's nothing to do since it might be the
2512                 complete sequence, and thus, something we don't want
2513                 copied.
2514             */
2515             if (data[i] == cont_seq[cmatch])
2516             {
2517                 cmatch++;
2518                 if (cmatch == strlen(cont_seq))
2519                 {
2520                     cmatch = 0; // complete match.  just reset the counter
2521
2522                     /*
2523                         it's possible for the ICS to not include the space
2524                         at the end of the last word, making our [correct]
2525                         join operation fuse two separate words.  the server
2526                         does this when the space occurs at the width setting.
2527                     */
2528                     if (!buf_len || buf[buf_len-1] != ' ')
2529                     {
2530                         *bp++ = ' ';
2531                         buf_len++;
2532                     }
2533                 }
2534                 continue;
2535             }
2536             else if (cmatch)
2537             {
2538                 /*
2539                     match failed, so we have to copy what matched before
2540                     falling through and copying this character.  In reality,
2541                     this will only ever be just the newline character, but
2542                     it doesn't hurt to be precise.
2543                 */
2544                 strncpy(bp, cont_seq, cmatch);
2545                 bp += cmatch;
2546                 buf_len += cmatch;
2547                 cmatch = 0;
2548             }
2549         }
2550
2551         // copy this char
2552         *bp++ = data[i];
2553         buf_len++;
2554     }
2555
2556         buf[buf_len] = NULLCHAR;
2557 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2558         next_out = 0;
2559         leftover_start = 0;
2560
2561         i = 0;
2562         while (i < buf_len) {
2563             /* Deal with part of the TELNET option negotiation
2564                protocol.  We refuse to do anything beyond the
2565                defaults, except that we allow the WILL ECHO option,
2566                which ICS uses to turn off password echoing when we are
2567                directly connected to it.  We reject this option
2568                if localLineEditing mode is on (always on in xboard)
2569                and we are talking to port 23, which might be a real
2570                telnet server that will try to keep WILL ECHO on permanently.
2571              */
2572             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2573                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2574                 unsigned char option;
2575                 oldi = i;
2576                 switch ((unsigned char) buf[++i]) {
2577                   case TN_WILL:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<WILL ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       case TN_ECHO:
2582                         if (appData.debugMode)
2583                           fprintf(debugFP, "ECHO ");
2584                         /* Reply only if this is a change, according
2585                            to the protocol rules. */
2586                         if (remoteEchoOption) break;
2587                         if (appData.localLineEditing &&
2588                             atoi(appData.icsPort) == TN_PORT) {
2589                             TelnetRequest(TN_DONT, TN_ECHO);
2590                         } else {
2591                             EchoOff();
2592                             TelnetRequest(TN_DO, TN_ECHO);
2593                             remoteEchoOption = TRUE;
2594                         }
2595                         break;
2596                       default:
2597                         if (appData.debugMode)
2598                           fprintf(debugFP, "%d ", option);
2599                         /* Whatever this is, we don't want it. */
2600                         TelnetRequest(TN_DONT, option);
2601                         break;
2602                     }
2603                     break;
2604                   case TN_WONT:
2605                     if (appData.debugMode)
2606                       fprintf(debugFP, "\n<WONT ");
2607                     switch (option = (unsigned char) buf[++i]) {
2608                       case TN_ECHO:
2609                         if (appData.debugMode)
2610                           fprintf(debugFP, "ECHO ");
2611                         /* Reply only if this is a change, according
2612                            to the protocol rules. */
2613                         if (!remoteEchoOption) break;
2614                         EchoOn();
2615                         TelnetRequest(TN_DONT, TN_ECHO);
2616                         remoteEchoOption = FALSE;
2617                         break;
2618                       default:
2619                         if (appData.debugMode)
2620                           fprintf(debugFP, "%d ", (unsigned char) option);
2621                         /* Whatever this is, it must already be turned
2622                            off, because we never agree to turn on
2623                            anything non-default, so according to the
2624                            protocol rules, we don't reply. */
2625                         break;
2626                     }
2627                     break;
2628                   case TN_DO:
2629                     if (appData.debugMode)
2630                       fprintf(debugFP, "\n<DO ");
2631                     switch (option = (unsigned char) buf[++i]) {
2632                       default:
2633                         /* Whatever this is, we refuse to do it. */
2634                         if (appData.debugMode)
2635                           fprintf(debugFP, "%d ", option);
2636                         TelnetRequest(TN_WONT, option);
2637                         break;
2638                     }
2639                     break;
2640                   case TN_DONT:
2641                     if (appData.debugMode)
2642                       fprintf(debugFP, "\n<DONT ");
2643                     switch (option = (unsigned char) buf[++i]) {
2644                       default:
2645                         if (appData.debugMode)
2646                           fprintf(debugFP, "%d ", option);
2647                         /* Whatever this is, we are already not doing
2648                            it, because we never agree to do anything
2649                            non-default, so according to the protocol
2650                            rules, we don't reply. */
2651                         break;
2652                     }
2653                     break;
2654                   case TN_IAC:
2655                     if (appData.debugMode)
2656                       fprintf(debugFP, "\n<IAC ");
2657                     /* Doubled IAC; pass it through */
2658                     i--;
2659                     break;
2660                   default:
2661                     if (appData.debugMode)
2662                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2663                     /* Drop all other telnet commands on the floor */
2664                     break;
2665                 }
2666                 if (oldi > next_out)
2667                   SendToPlayer(&buf[next_out], oldi - next_out);
2668                 if (++i > next_out)
2669                   next_out = i;
2670                 continue;
2671             }
2672
2673             /* OK, this at least will *usually* work */
2674             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2675                 loggedOn = TRUE;
2676             }
2677
2678             if (loggedOn && !intfSet) {
2679                 if (ics_type == ICS_ICC) {
2680                   snprintf(str, MSG_SIZ,
2681                           "/set-quietly interface %s\n/set-quietly style 12\n",
2682                           programVersion);
2683                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2684                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2685                 } else if (ics_type == ICS_CHESSNET) {
2686                   snprintf(str, MSG_SIZ, "/style 12\n");
2687                 } else {
2688                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2689                   strcat(str, programVersion);
2690                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2691                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2692                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2693 #ifdef WIN32
2694                   strcat(str, "$iset nohighlight 1\n");
2695 #endif
2696                   strcat(str, "$iset lock 1\n$style 12\n");
2697                 }
2698                 SendToICS(str);
2699                 NotifyFrontendLogin();
2700                 intfSet = TRUE;
2701             }
2702
2703             if (started == STARTED_COMMENT) {
2704                 /* Accumulate characters in comment */
2705                 parse[parse_pos++] = buf[i];
2706                 if (buf[i] == '\n') {
2707                     parse[parse_pos] = NULLCHAR;
2708                     if(chattingPartner>=0) {
2709                         char mess[MSG_SIZ];
2710                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2711                         OutputChatMessage(chattingPartner, mess);
2712                         chattingPartner = -1;
2713                         next_out = i+1; // [HGM] suppress printing in ICS window
2714                     } else
2715                     if(!suppressKibitz) // [HGM] kibitz
2716                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2717                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2718                         int nrDigit = 0, nrAlph = 0, j;
2719                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2720                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2721                         parse[parse_pos] = NULLCHAR;
2722                         // try to be smart: if it does not look like search info, it should go to
2723                         // ICS interaction window after all, not to engine-output window.
2724                         for(j=0; j<parse_pos; j++) { // count letters and digits
2725                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2726                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2727                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2728                         }
2729                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2730                             int depth=0; float score;
2731                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2732                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2733                                 pvInfoList[forwardMostMove-1].depth = depth;
2734                                 pvInfoList[forwardMostMove-1].score = 100*score;
2735                             }
2736                             OutputKibitz(suppressKibitz, parse);
2737                         } else {
2738                             char tmp[MSG_SIZ];
2739                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2740                             SendToPlayer(tmp, strlen(tmp));
2741                         }
2742                         next_out = i+1; // [HGM] suppress printing in ICS window
2743                     }
2744                     started = STARTED_NONE;
2745                 } else {
2746                     /* Don't match patterns against characters in comment */
2747                     i++;
2748                     continue;
2749                 }
2750             }
2751             if (started == STARTED_CHATTER) {
2752                 if (buf[i] != '\n') {
2753                     /* Don't match patterns against characters in chatter */
2754                     i++;
2755                     continue;
2756                 }
2757                 started = STARTED_NONE;
2758                 if(suppressKibitz) next_out = i+1;
2759             }
2760
2761             /* Kludge to deal with rcmd protocol */
2762             if (firstTime && looking_at(buf, &i, "\001*")) {
2763                 DisplayFatalError(&buf[1], 0, 1);
2764                 continue;
2765             } else {
2766                 firstTime = FALSE;
2767             }
2768
2769             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2770                 ics_type = ICS_ICC;
2771                 ics_prefix = "/";
2772                 if (appData.debugMode)
2773                   fprintf(debugFP, "ics_type %d\n", ics_type);
2774                 continue;
2775             }
2776             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2777                 ics_type = ICS_FICS;
2778                 ics_prefix = "$";
2779                 if (appData.debugMode)
2780                   fprintf(debugFP, "ics_type %d\n", ics_type);
2781                 continue;
2782             }
2783             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2784                 ics_type = ICS_CHESSNET;
2785                 ics_prefix = "/";
2786                 if (appData.debugMode)
2787                   fprintf(debugFP, "ics_type %d\n", ics_type);
2788                 continue;
2789             }
2790
2791             if (!loggedOn &&
2792                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2793                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2794                  looking_at(buf, &i, "will be \"*\""))) {
2795               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2796               continue;
2797             }
2798
2799             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2800               char buf[MSG_SIZ];
2801               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2802               DisplayIcsInteractionTitle(buf);
2803               have_set_title = TRUE;
2804             }
2805
2806             /* skip finger notes */
2807             if (started == STARTED_NONE &&
2808                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2809                  (buf[i] == '1' && buf[i+1] == '0')) &&
2810                 buf[i+2] == ':' && buf[i+3] == ' ') {
2811               started = STARTED_CHATTER;
2812               i += 3;
2813               continue;
2814             }
2815
2816             oldi = i;
2817             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2818             if(appData.seekGraph) {
2819                 if(soughtPending && MatchSoughtLine(buf+i)) {
2820                     i = strstr(buf+i, "rated") - buf;
2821                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2822                     next_out = leftover_start = i;
2823                     started = STARTED_CHATTER;
2824                     suppressKibitz = TRUE;
2825                     continue;
2826                 }
2827                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2828                         && looking_at(buf, &i, "* ads displayed")) {
2829                     soughtPending = FALSE;
2830                     seekGraphUp = TRUE;
2831                     DrawSeekGraph();
2832                     continue;
2833                 }
2834                 if(appData.autoRefresh) {
2835                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2836                         int s = (ics_type == ICS_ICC); // ICC format differs
2837                         if(seekGraphUp)
2838                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2839                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2840                         looking_at(buf, &i, "*% "); // eat prompt
2841                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2842                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2843                         next_out = i; // suppress
2844                         continue;
2845                     }
2846                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2847                         char *p = star_match[0];
2848                         while(*p) {
2849                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2850                             while(*p && *p++ != ' '); // next
2851                         }
2852                         looking_at(buf, &i, "*% "); // eat prompt
2853                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2854                         next_out = i;
2855                         continue;
2856                     }
2857                 }
2858             }
2859
2860             /* skip formula vars */
2861             if (started == STARTED_NONE &&
2862                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2863               started = STARTED_CHATTER;
2864               i += 3;
2865               continue;
2866             }
2867
2868             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2869             if (appData.autoKibitz && started == STARTED_NONE &&
2870                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2871                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2872                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2873                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2874                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2875                         suppressKibitz = TRUE;
2876                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2877                         next_out = i;
2878                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2879                                 && (gameMode == IcsPlayingWhite)) ||
2880                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2881                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2882                             started = STARTED_CHATTER; // own kibitz we simply discard
2883                         else {
2884                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2885                             parse_pos = 0; parse[0] = NULLCHAR;
2886                             savingComment = TRUE;
2887                             suppressKibitz = gameMode != IcsObserving ? 2 :
2888                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2889                         }
2890                         continue;
2891                 } else
2892                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2893                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2894                          && atoi(star_match[0])) {
2895                     // suppress the acknowledgements of our own autoKibitz
2896                     char *p;
2897                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2898                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2899                     SendToPlayer(star_match[0], strlen(star_match[0]));
2900                     if(looking_at(buf, &i, "*% ")) // eat prompt
2901                         suppressKibitz = FALSE;
2902                     next_out = i;
2903                     continue;
2904                 }
2905             } // [HGM] kibitz: end of patch
2906
2907             // [HGM] chat: intercept tells by users for which we have an open chat window
2908             channel = -1;
2909             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2910                                            looking_at(buf, &i, "* whispers:") ||
2911                                            looking_at(buf, &i, "* kibitzes:") ||
2912                                            looking_at(buf, &i, "* shouts:") ||
2913                                            looking_at(buf, &i, "* c-shouts:") ||
2914                                            looking_at(buf, &i, "--> * ") ||
2915                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2916                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2917                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2918                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2919                 int p;
2920                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2921                 chattingPartner = -1;
2922
2923                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2924                 for(p=0; p<MAX_CHAT; p++) {
2925                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2926                     talker[0] = '['; strcat(talker, "] ");
2927                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2928                     chattingPartner = p; break;
2929                     }
2930                 } else
2931                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2932                 for(p=0; p<MAX_CHAT; p++) {
2933                     if(!strcmp("kibitzes", chatPartner[p])) {
2934                         talker[0] = '['; strcat(talker, "] ");
2935                         chattingPartner = p; break;
2936                     }
2937                 } else
2938                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2939                 for(p=0; p<MAX_CHAT; p++) {
2940                     if(!strcmp("whispers", chatPartner[p])) {
2941                         talker[0] = '['; strcat(talker, "] ");
2942                         chattingPartner = p; break;
2943                     }
2944                 } else
2945                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2946                   if(buf[i-8] == '-' && buf[i-3] == 't')
2947                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2948                     if(!strcmp("c-shouts", chatPartner[p])) {
2949                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2950                         chattingPartner = p; break;
2951                     }
2952                   }
2953                   if(chattingPartner < 0)
2954                   for(p=0; p<MAX_CHAT; p++) {
2955                     if(!strcmp("shouts", chatPartner[p])) {
2956                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2957                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2958                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2959                         chattingPartner = p; break;
2960                     }
2961                   }
2962                 }
2963                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2964                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2965                     talker[0] = 0; Colorize(ColorTell, FALSE);
2966                     chattingPartner = p; break;
2967                 }
2968                 if(chattingPartner<0) i = oldi; else {
2969                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2970                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2971                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2972                     started = STARTED_COMMENT;
2973                     parse_pos = 0; parse[0] = NULLCHAR;
2974                     savingComment = 3 + chattingPartner; // counts as TRUE
2975                     suppressKibitz = TRUE;
2976                     continue;
2977                 }
2978             } // [HGM] chat: end of patch
2979
2980             if (appData.zippyTalk || appData.zippyPlay) {
2981                 /* [DM] Backup address for color zippy lines */
2982                 backup = i;
2983 #if ZIPPY
2984                if (loggedOn == TRUE)
2985                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2986                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2987 #endif
2988             } // [DM] 'else { ' deleted
2989                 if (
2990                     /* Regular tells and says */
2991                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2992                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2993                     looking_at(buf, &i, "* says: ") ||
2994                     /* Don't color "message" or "messages" output */
2995                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2996                     looking_at(buf, &i, "*. * at *:*: ") ||
2997                     looking_at(buf, &i, "--* (*:*): ") ||
2998                     /* Message notifications (same color as tells) */
2999                     looking_at(buf, &i, "* has left a message ") ||
3000                     looking_at(buf, &i, "* just sent you a message:\n") ||
3001                     /* Whispers and kibitzes */
3002                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3003                     looking_at(buf, &i, "* kibitzes: ") ||
3004                     /* Channel tells */
3005                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3006
3007                   if (tkind == 1 && strchr(star_match[0], ':')) {
3008                       /* Avoid "tells you:" spoofs in channels */
3009                      tkind = 3;
3010                   }
3011                   if (star_match[0][0] == NULLCHAR ||
3012                       strchr(star_match[0], ' ') ||
3013                       (tkind == 3 && strchr(star_match[1], ' '))) {
3014                     /* Reject bogus matches */
3015                     i = oldi;
3016                   } else {
3017                     if (appData.colorize) {
3018                       if (oldi > next_out) {
3019                         SendToPlayer(&buf[next_out], oldi - next_out);
3020                         next_out = oldi;
3021                       }
3022                       switch (tkind) {
3023                       case 1:
3024                         Colorize(ColorTell, FALSE);
3025                         curColor = ColorTell;
3026                         break;
3027                       case 2:
3028                         Colorize(ColorKibitz, FALSE);
3029                         curColor = ColorKibitz;
3030                         break;
3031                       case 3:
3032                         p = strrchr(star_match[1], '(');
3033                         if (p == NULL) {
3034                           p = star_match[1];
3035                         } else {
3036                           p++;
3037                         }
3038                         if (atoi(p) == 1) {
3039                           Colorize(ColorChannel1, FALSE);
3040                           curColor = ColorChannel1;
3041                         } else {
3042                           Colorize(ColorChannel, FALSE);
3043                           curColor = ColorChannel;
3044                         }
3045                         break;
3046                       case 5:
3047                         curColor = ColorNormal;
3048                         break;
3049                       }
3050                     }
3051                     if (started == STARTED_NONE && appData.autoComment &&
3052                         (gameMode == IcsObserving ||
3053                          gameMode == IcsPlayingWhite ||
3054                          gameMode == IcsPlayingBlack)) {
3055                       parse_pos = i - oldi;
3056                       memcpy(parse, &buf[oldi], parse_pos);
3057                       parse[parse_pos] = NULLCHAR;
3058                       started = STARTED_COMMENT;
3059                       savingComment = TRUE;
3060                     } else {
3061                       started = STARTED_CHATTER;
3062                       savingComment = FALSE;
3063                     }
3064                     loggedOn = TRUE;
3065                     continue;
3066                   }
3067                 }
3068
3069                 if (looking_at(buf, &i, "* s-shouts: ") ||
3070                     looking_at(buf, &i, "* c-shouts: ")) {
3071                     if (appData.colorize) {
3072                         if (oldi > next_out) {
3073                             SendToPlayer(&buf[next_out], oldi - next_out);
3074                             next_out = oldi;
3075                         }
3076                         Colorize(ColorSShout, FALSE);
3077                         curColor = ColorSShout;
3078                     }
3079                     loggedOn = TRUE;
3080                     started = STARTED_CHATTER;
3081                     continue;
3082                 }
3083
3084                 if (looking_at(buf, &i, "--->")) {
3085                     loggedOn = TRUE;
3086                     continue;
3087                 }
3088
3089                 if (looking_at(buf, &i, "* shouts: ") ||
3090                     looking_at(buf, &i, "--> ")) {
3091                     if (appData.colorize) {
3092                         if (oldi > next_out) {
3093                             SendToPlayer(&buf[next_out], oldi - next_out);
3094                             next_out = oldi;
3095                         }
3096                         Colorize(ColorShout, FALSE);
3097                         curColor = ColorShout;
3098                     }
3099                     loggedOn = TRUE;
3100                     started = STARTED_CHATTER;
3101                     continue;
3102                 }
3103
3104                 if (looking_at( buf, &i, "Challenge:")) {
3105                     if (appData.colorize) {
3106                         if (oldi > next_out) {
3107                             SendToPlayer(&buf[next_out], oldi - next_out);
3108                             next_out = oldi;
3109                         }
3110                         Colorize(ColorChallenge, FALSE);
3111                         curColor = ColorChallenge;
3112                     }
3113                     loggedOn = TRUE;
3114                     continue;
3115                 }
3116
3117                 if (looking_at(buf, &i, "* offers you") ||
3118                     looking_at(buf, &i, "* offers to be") ||
3119                     looking_at(buf, &i, "* would like to") ||
3120                     looking_at(buf, &i, "* requests to") ||
3121                     looking_at(buf, &i, "Your opponent offers") ||
3122                     looking_at(buf, &i, "Your opponent requests")) {
3123
3124                     if (appData.colorize) {
3125                         if (oldi > next_out) {
3126                             SendToPlayer(&buf[next_out], oldi - next_out);
3127                             next_out = oldi;
3128                         }
3129                         Colorize(ColorRequest, FALSE);
3130                         curColor = ColorRequest;
3131                     }
3132                     continue;
3133                 }
3134
3135                 if (looking_at(buf, &i, "* (*) seeking")) {
3136                     if (appData.colorize) {
3137                         if (oldi > next_out) {
3138                             SendToPlayer(&buf[next_out], oldi - next_out);
3139                             next_out = oldi;
3140                         }
3141                         Colorize(ColorSeek, FALSE);
3142                         curColor = ColorSeek;
3143                     }
3144                     continue;
3145             }
3146
3147             if (looking_at(buf, &i, "\\   ")) {
3148                 if (prevColor != ColorNormal) {
3149                     if (oldi > next_out) {
3150                         SendToPlayer(&buf[next_out], oldi - next_out);
3151                         next_out = oldi;
3152                     }
3153                     Colorize(prevColor, TRUE);
3154                     curColor = prevColor;
3155                 }
3156                 if (savingComment) {
3157                     parse_pos = i - oldi;
3158                     memcpy(parse, &buf[oldi], parse_pos);
3159                     parse[parse_pos] = NULLCHAR;
3160                     started = STARTED_COMMENT;
3161                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3162                         chattingPartner = savingComment - 3; // kludge to remember the box
3163                 } else {
3164                     started = STARTED_CHATTER;
3165                 }
3166                 continue;
3167             }
3168
3169             if (looking_at(buf, &i, "Black Strength :") ||
3170                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3171                 looking_at(buf, &i, "<10>") ||
3172                 looking_at(buf, &i, "#@#")) {
3173                 /* Wrong board style */
3174                 loggedOn = TRUE;
3175                 SendToICS(ics_prefix);
3176                 SendToICS("set style 12\n");
3177                 SendToICS(ics_prefix);
3178                 SendToICS("refresh\n");
3179                 continue;
3180             }
3181
3182             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3183                 ICSInitScript();
3184                 have_sent_ICS_logon = 1;
3185                 continue;
3186             }
3187
3188             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3189                 (looking_at(buf, &i, "\n<12> ") ||
3190                  looking_at(buf, &i, "<12> "))) {
3191                 loggedOn = TRUE;
3192                 if (oldi > next_out) {
3193                     SendToPlayer(&buf[next_out], oldi - next_out);
3194                 }
3195                 next_out = i;
3196                 started = STARTED_BOARD;
3197                 parse_pos = 0;
3198                 continue;
3199             }
3200
3201             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3202                 looking_at(buf, &i, "<b1> ")) {
3203                 if (oldi > next_out) {
3204                     SendToPlayer(&buf[next_out], oldi - next_out);
3205                 }
3206                 next_out = i;
3207                 started = STARTED_HOLDINGS;
3208                 parse_pos = 0;
3209                 continue;
3210             }
3211
3212             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3213                 loggedOn = TRUE;
3214                 /* Header for a move list -- first line */
3215
3216                 switch (ics_getting_history) {
3217                   case H_FALSE:
3218                     switch (gameMode) {
3219                       case IcsIdle:
3220                       case BeginningOfGame:
3221                         /* User typed "moves" or "oldmoves" while we
3222                            were idle.  Pretend we asked for these
3223                            moves and soak them up so user can step
3224                            through them and/or save them.
3225                            */
3226                         Reset(FALSE, TRUE);
3227                         gameMode = IcsObserving;
3228                         ModeHighlight();
3229                         ics_gamenum = -1;
3230                         ics_getting_history = H_GOT_UNREQ_HEADER;
3231                         break;
3232                       case EditGame: /*?*/
3233                       case EditPosition: /*?*/
3234                         /* Should above feature work in these modes too? */
3235                         /* For now it doesn't */
3236                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3237                         break;
3238                       default:
3239                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3240                         break;
3241                     }
3242                     break;
3243                   case H_REQUESTED:
3244                     /* Is this the right one? */
3245                     if (gameInfo.white && gameInfo.black &&
3246                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3247                         strcmp(gameInfo.black, star_match[2]) == 0) {
3248                         /* All is well */
3249                         ics_getting_history = H_GOT_REQ_HEADER;
3250                     }
3251                     break;
3252                   case H_GOT_REQ_HEADER:
3253                   case H_GOT_UNREQ_HEADER:
3254                   case H_GOT_UNWANTED_HEADER:
3255                   case H_GETTING_MOVES:
3256                     /* Should not happen */
3257                     DisplayError(_("Error gathering move list: two headers"), 0);
3258                     ics_getting_history = H_FALSE;
3259                     break;
3260                 }
3261
3262                 /* Save player ratings into gameInfo if needed */
3263                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3264                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3265                     (gameInfo.whiteRating == -1 ||
3266                      gameInfo.blackRating == -1)) {
3267
3268                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3269                     gameInfo.blackRating = string_to_rating(star_match[3]);
3270                     if (appData.debugMode)
3271                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3272                               gameInfo.whiteRating, gameInfo.blackRating);
3273                 }
3274                 continue;
3275             }
3276
3277             if (looking_at(buf, &i,
3278               "* * match, initial time: * minute*, increment: * second")) {
3279                 /* Header for a move list -- second line */
3280                 /* Initial board will follow if this is a wild game */
3281                 if (gameInfo.event != NULL) free(gameInfo.event);
3282                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3283                 gameInfo.event = StrSave(str);
3284                 /* [HGM] we switched variant. Translate boards if needed. */
3285                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3286                 continue;
3287             }
3288
3289             if (looking_at(buf, &i, "Move  ")) {
3290                 /* Beginning of a move list */
3291                 switch (ics_getting_history) {
3292                   case H_FALSE:
3293                     /* Normally should not happen */
3294                     /* Maybe user hit reset while we were parsing */
3295                     break;
3296                   case H_REQUESTED:
3297                     /* Happens if we are ignoring a move list that is not
3298                      * the one we just requested.  Common if the user
3299                      * tries to observe two games without turning off
3300                      * getMoveList */
3301                     break;
3302                   case H_GETTING_MOVES:
3303                     /* Should not happen */
3304                     DisplayError(_("Error gathering move list: nested"), 0);
3305                     ics_getting_history = H_FALSE;
3306                     break;
3307                   case H_GOT_REQ_HEADER:
3308                     ics_getting_history = H_GETTING_MOVES;
3309                     started = STARTED_MOVES;
3310                     parse_pos = 0;
3311                     if (oldi > next_out) {
3312                         SendToPlayer(&buf[next_out], oldi - next_out);
3313                     }
3314                     break;
3315                   case H_GOT_UNREQ_HEADER:
3316                     ics_getting_history = H_GETTING_MOVES;
3317                     started = STARTED_MOVES_NOHIDE;
3318                     parse_pos = 0;
3319                     break;
3320                   case H_GOT_UNWANTED_HEADER:
3321                     ics_getting_history = H_FALSE;
3322                     break;
3323                 }
3324                 continue;
3325             }
3326
3327             if (looking_at(buf, &i, "% ") ||
3328                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3329                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3330                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3331                     soughtPending = FALSE;
3332                     seekGraphUp = TRUE;
3333                     DrawSeekGraph();
3334                 }
3335                 if(suppressKibitz) next_out = i;
3336                 savingComment = FALSE;
3337                 suppressKibitz = 0;
3338                 switch (started) {
3339                   case STARTED_MOVES:
3340                   case STARTED_MOVES_NOHIDE:
3341                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3342                     parse[parse_pos + i - oldi] = NULLCHAR;
3343                     ParseGameHistory(parse);
3344 #if ZIPPY
3345                     if (appData.zippyPlay && first.initDone) {
3346                         FeedMovesToProgram(&first, forwardMostMove);
3347                         if (gameMode == IcsPlayingWhite) {
3348                             if (WhiteOnMove(forwardMostMove)) {
3349                                 if (first.sendTime) {
3350                                   if (first.useColors) {
3351                                     SendToProgram("black\n", &first);
3352                                   }
3353                                   SendTimeRemaining(&first, TRUE);
3354                                 }
3355                                 if (first.useColors) {
3356                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3357                                 }
3358                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3359                                 first.maybeThinking = TRUE;
3360                             } else {
3361                                 if (first.usePlayother) {
3362                                   if (first.sendTime) {
3363                                     SendTimeRemaining(&first, TRUE);
3364                                   }
3365                                   SendToProgram("playother\n", &first);
3366                                   firstMove = FALSE;
3367                                 } else {
3368                                   firstMove = TRUE;
3369                                 }
3370                             }
3371                         } else if (gameMode == IcsPlayingBlack) {
3372                             if (!WhiteOnMove(forwardMostMove)) {
3373                                 if (first.sendTime) {
3374                                   if (first.useColors) {
3375                                     SendToProgram("white\n", &first);
3376                                   }
3377                                   SendTimeRemaining(&first, FALSE);
3378                                 }
3379                                 if (first.useColors) {
3380                                   SendToProgram("black\n", &first);
3381                                 }
3382                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3383                                 first.maybeThinking = TRUE;
3384                             } else {
3385                                 if (first.usePlayother) {
3386                                   if (first.sendTime) {
3387                                     SendTimeRemaining(&first, FALSE);
3388                                   }
3389                                   SendToProgram("playother\n", &first);
3390                                   firstMove = FALSE;
3391                                 } else {
3392                                   firstMove = TRUE;
3393                                 }
3394                             }
3395                         }
3396                     }
3397 #endif
3398                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3399                         /* Moves came from oldmoves or moves command
3400                            while we weren't doing anything else.
3401                            */
3402                         currentMove = forwardMostMove;
3403                         ClearHighlights();/*!!could figure this out*/
3404                         flipView = appData.flipView;
3405                         DrawPosition(TRUE, boards[currentMove]);
3406                         DisplayBothClocks();
3407                         snprintf(str, MSG_SIZ, "%s vs. %s",
3408                                 gameInfo.white, gameInfo.black);
3409                         DisplayTitle(str);
3410                         gameMode = IcsIdle;
3411                     } else {
3412                         /* Moves were history of an active game */
3413                         if (gameInfo.resultDetails != NULL) {
3414                             free(gameInfo.resultDetails);
3415                             gameInfo.resultDetails = NULL;
3416                         }
3417                     }
3418                     HistorySet(parseList, backwardMostMove,
3419                                forwardMostMove, currentMove-1);
3420                     DisplayMove(currentMove - 1);
3421                     if (started == STARTED_MOVES) next_out = i;
3422                     started = STARTED_NONE;
3423                     ics_getting_history = H_FALSE;
3424                     break;
3425
3426                   case STARTED_OBSERVE:
3427                     started = STARTED_NONE;
3428                     SendToICS(ics_prefix);
3429                     SendToICS("refresh\n");
3430                     break;
3431
3432                   default:
3433                     break;
3434                 }
3435                 if(bookHit) { // [HGM] book: simulate book reply
3436                     static char bookMove[MSG_SIZ]; // a bit generous?
3437
3438                     programStats.nodes = programStats.depth = programStats.time =
3439                     programStats.score = programStats.got_only_move = 0;
3440                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3441
3442                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3443                     strcat(bookMove, bookHit);
3444                     HandleMachineMove(bookMove, &first);
3445                 }
3446                 continue;
3447             }
3448
3449             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3450                  started == STARTED_HOLDINGS ||
3451                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3452                 /* Accumulate characters in move list or board */
3453                 parse[parse_pos++] = buf[i];
3454             }
3455
3456             /* Start of game messages.  Mostly we detect start of game
3457                when the first board image arrives.  On some versions
3458                of the ICS, though, we need to do a "refresh" after starting
3459                to observe in order to get the current board right away. */
3460             if (looking_at(buf, &i, "Adding game * to observation list")) {
3461                 started = STARTED_OBSERVE;
3462                 continue;
3463             }
3464
3465             /* Handle auto-observe */
3466             if (appData.autoObserve &&
3467                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3468                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3469                 char *player;
3470                 /* Choose the player that was highlighted, if any. */
3471                 if (star_match[0][0] == '\033' ||
3472                     star_match[1][0] != '\033') {
3473                     player = star_match[0];
3474                 } else {
3475                     player = star_match[2];
3476                 }
3477                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3478                         ics_prefix, StripHighlightAndTitle(player));
3479                 SendToICS(str);
3480
3481                 /* Save ratings from notify string */
3482                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3483                 player1Rating = string_to_rating(star_match[1]);
3484                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3485                 player2Rating = string_to_rating(star_match[3]);
3486
3487                 if (appData.debugMode)
3488                   fprintf(debugFP,
3489                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3490                           player1Name, player1Rating,
3491                           player2Name, player2Rating);
3492
3493                 continue;
3494             }
3495
3496             /* Deal with automatic examine mode after a game,
3497                and with IcsObserving -> IcsExamining transition */
3498             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3499                 looking_at(buf, &i, "has made you an examiner of game *")) {
3500
3501                 int gamenum = atoi(star_match[0]);
3502                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3503                     gamenum == ics_gamenum) {
3504                     /* We were already playing or observing this game;
3505                        no need to refetch history */
3506                     gameMode = IcsExamining;
3507                     if (pausing) {
3508                         pauseExamForwardMostMove = forwardMostMove;
3509                     } else if (currentMove < forwardMostMove) {
3510                         ForwardInner(forwardMostMove);
3511                     }
3512                 } else {
3513                     /* I don't think this case really can happen */
3514                     SendToICS(ics_prefix);
3515                     SendToICS("refresh\n");
3516                 }
3517                 continue;
3518             }
3519
3520             /* Error messages */
3521 //          if (ics_user_moved) {
3522             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3523                 if (looking_at(buf, &i, "Illegal move") ||
3524                     looking_at(buf, &i, "Not a legal move") ||
3525                     looking_at(buf, &i, "Your king is in check") ||
3526                     looking_at(buf, &i, "It isn't your turn") ||
3527                     looking_at(buf, &i, "It is not your move")) {
3528                     /* Illegal move */
3529                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3530                         currentMove = forwardMostMove-1;
3531                         DisplayMove(currentMove - 1); /* before DMError */
3532                         DrawPosition(FALSE, boards[currentMove]);
3533                         SwitchClocks(forwardMostMove-1); // [HGM] race
3534                         DisplayBothClocks();
3535                     }
3536                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3537                     ics_user_moved = 0;
3538                     continue;
3539                 }
3540             }
3541
3542             if (looking_at(buf, &i, "still have time") ||
3543                 looking_at(buf, &i, "not out of time") ||
3544                 looking_at(buf, &i, "either player is out of time") ||
3545                 looking_at(buf, &i, "has timeseal; checking")) {
3546                 /* We must have called his flag a little too soon */
3547                 whiteFlag = blackFlag = FALSE;
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "added * seconds to") ||
3552                 looking_at(buf, &i, "seconds were added to")) {
3553                 /* Update the clocks */
3554                 SendToICS(ics_prefix);
3555                 SendToICS("refresh\n");
3556                 continue;
3557             }
3558
3559             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3560                 ics_clock_paused = TRUE;
3561                 StopClocks();
3562                 continue;
3563             }
3564
3565             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3566                 ics_clock_paused = FALSE;
3567                 StartClocks();
3568                 continue;
3569             }
3570
3571             /* Grab player ratings from the Creating: message.
3572                Note we have to check for the special case when
3573                the ICS inserts things like [white] or [black]. */
3574             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3575                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3576                 /* star_matches:
3577                    0    player 1 name (not necessarily white)
3578                    1    player 1 rating
3579                    2    empty, white, or black (IGNORED)
3580                    3    player 2 name (not necessarily black)
3581                    4    player 2 rating
3582
3583                    The names/ratings are sorted out when the game
3584                    actually starts (below).
3585                 */
3586                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3587                 player1Rating = string_to_rating(star_match[1]);
3588                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3589                 player2Rating = string_to_rating(star_match[4]);
3590
3591                 if (appData.debugMode)
3592                   fprintf(debugFP,
3593                           "Ratings from 'Creating:' %s %d, %s %d\n",
3594                           player1Name, player1Rating,
3595                           player2Name, player2Rating);
3596
3597                 continue;
3598             }
3599
3600             /* Improved generic start/end-of-game messages */
3601             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3602                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3603                 /* If tkind == 0: */
3604                 /* star_match[0] is the game number */
3605                 /*           [1] is the white player's name */
3606                 /*           [2] is the black player's name */
3607                 /* For end-of-game: */
3608                 /*           [3] is the reason for the game end */
3609                 /*           [4] is a PGN end game-token, preceded by " " */
3610                 /* For start-of-game: */
3611                 /*           [3] begins with "Creating" or "Continuing" */
3612                 /*           [4] is " *" or empty (don't care). */
3613                 int gamenum = atoi(star_match[0]);
3614                 char *whitename, *blackname, *why, *endtoken;
3615                 ChessMove endtype = EndOfFile;
3616
3617                 if (tkind == 0) {
3618                   whitename = star_match[1];
3619                   blackname = star_match[2];
3620                   why = star_match[3];
3621                   endtoken = star_match[4];
3622                 } else {
3623                   whitename = star_match[1];
3624                   blackname = star_match[3];
3625                   why = star_match[5];
3626                   endtoken = star_match[6];
3627                 }
3628
3629                 /* Game start messages */
3630                 if (strncmp(why, "Creating ", 9) == 0 ||
3631                     strncmp(why, "Continuing ", 11) == 0) {
3632                     gs_gamenum = gamenum;
3633                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3634                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3635 #if ZIPPY
3636                     if (appData.zippyPlay) {
3637                         ZippyGameStart(whitename, blackname);
3638                     }
3639 #endif /*ZIPPY*/
3640                     partnerBoardValid = FALSE; // [HGM] bughouse
3641                     continue;
3642                 }
3643
3644                 /* Game end messages */
3645                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3646                     ics_gamenum != gamenum) {
3647                     continue;
3648                 }
3649                 while (endtoken[0] == ' ') endtoken++;
3650                 switch (endtoken[0]) {
3651                   case '*':
3652                   default:
3653                     endtype = GameUnfinished;
3654                     break;
3655                   case '0':
3656                     endtype = BlackWins;
3657                     break;
3658                   case '1':
3659                     if (endtoken[1] == '/')
3660                       endtype = GameIsDrawn;
3661                     else
3662                       endtype = WhiteWins;
3663                     break;
3664                 }
3665                 GameEnds(endtype, why, GE_ICS);
3666 #if ZIPPY
3667                 if (appData.zippyPlay && first.initDone) {
3668                     ZippyGameEnd(endtype, why);
3669                     if (first.pr == NULL) {
3670                       /* Start the next process early so that we'll
3671                          be ready for the next challenge */
3672                       StartChessProgram(&first);
3673                     }
3674                     /* Send "new" early, in case this command takes
3675                        a long time to finish, so that we'll be ready
3676                        for the next challenge. */
3677                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3678                     Reset(TRUE, TRUE);
3679                 }
3680 #endif /*ZIPPY*/
3681                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3682                 continue;
3683             }
3684
3685             if (looking_at(buf, &i, "Removing game * from observation") ||
3686                 looking_at(buf, &i, "no longer observing game *") ||
3687                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3688                 if (gameMode == IcsObserving &&
3689                     atoi(star_match[0]) == ics_gamenum)
3690                   {
3691                       /* icsEngineAnalyze */
3692                       if (appData.icsEngineAnalyze) {
3693                             ExitAnalyzeMode();
3694                             ModeHighlight();
3695                       }
3696                       StopClocks();
3697                       gameMode = IcsIdle;
3698                       ics_gamenum = -1;
3699                       ics_user_moved = FALSE;
3700                   }
3701                 continue;
3702             }
3703
3704             if (looking_at(buf, &i, "no longer examining game *")) {
3705                 if (gameMode == IcsExamining &&
3706                     atoi(star_match[0]) == ics_gamenum)
3707                   {
3708                       gameMode = IcsIdle;
3709                       ics_gamenum = -1;
3710                       ics_user_moved = FALSE;
3711                   }
3712                 continue;
3713             }
3714
3715             /* Advance leftover_start past any newlines we find,
3716                so only partial lines can get reparsed */
3717             if (looking_at(buf, &i, "\n")) {
3718                 prevColor = curColor;
3719                 if (curColor != ColorNormal) {
3720                     if (oldi > next_out) {
3721                         SendToPlayer(&buf[next_out], oldi - next_out);
3722                         next_out = oldi;
3723                     }
3724                     Colorize(ColorNormal, FALSE);
3725                     curColor = ColorNormal;
3726                 }
3727                 if (started == STARTED_BOARD) {
3728                     started = STARTED_NONE;
3729                     parse[parse_pos] = NULLCHAR;
3730                     ParseBoard12(parse);
3731                     ics_user_moved = 0;
3732
3733                     /* Send premove here */
3734                     if (appData.premove) {
3735                       char str[MSG_SIZ];
3736                       if (currentMove == 0 &&
3737                           gameMode == IcsPlayingWhite &&
3738                           appData.premoveWhite) {
3739                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3740                         if (appData.debugMode)
3741                           fprintf(debugFP, "Sending premove:\n");
3742                         SendToICS(str);
3743                       } else if (currentMove == 1 &&
3744                                  gameMode == IcsPlayingBlack &&
3745                                  appData.premoveBlack) {
3746                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3747                         if (appData.debugMode)
3748                           fprintf(debugFP, "Sending premove:\n");
3749                         SendToICS(str);
3750                       } else if (gotPremove) {
3751                         gotPremove = 0;
3752                         ClearPremoveHighlights();
3753                         if (appData.debugMode)
3754                           fprintf(debugFP, "Sending premove:\n");
3755                           UserMoveEvent(premoveFromX, premoveFromY,
3756                                         premoveToX, premoveToY,
3757                                         premovePromoChar);
3758                       }
3759                     }
3760
3761                     /* Usually suppress following prompt */
3762                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3763                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3764                         if (looking_at(buf, &i, "*% ")) {
3765                             savingComment = FALSE;
3766                             suppressKibitz = 0;
3767                         }
3768                     }
3769                     next_out = i;
3770                 } else if (started == STARTED_HOLDINGS) {
3771                     int gamenum;
3772                     char new_piece[MSG_SIZ];
3773                     started = STARTED_NONE;
3774                     parse[parse_pos] = NULLCHAR;
3775                     if (appData.debugMode)
3776                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3777                                                         parse, currentMove);
3778                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3779                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3780                         if (gameInfo.variant == VariantNormal) {
3781                           /* [HGM] We seem to switch variant during a game!
3782                            * Presumably no holdings were displayed, so we have
3783                            * to move the position two files to the right to
3784                            * create room for them!
3785                            */
3786                           VariantClass newVariant;
3787                           switch(gameInfo.boardWidth) { // base guess on board width
3788                                 case 9:  newVariant = VariantShogi; break;
3789                                 case 10: newVariant = VariantGreat; break;
3790                                 default: newVariant = VariantCrazyhouse; break;
3791                           }
3792                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3793                           /* Get a move list just to see the header, which
3794                              will tell us whether this is really bug or zh */
3795                           if (ics_getting_history == H_FALSE) {
3796                             ics_getting_history = H_REQUESTED;
3797                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3798                             SendToICS(str);
3799                           }
3800                         }
3801                         new_piece[0] = NULLCHAR;
3802                         sscanf(parse, "game %d white [%s black [%s <- %s",
3803                                &gamenum, white_holding, black_holding,
3804                                new_piece);
3805                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3806                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3807                         /* [HGM] copy holdings to board holdings area */
3808                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3809                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3810                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3811 #if ZIPPY
3812                         if (appData.zippyPlay && first.initDone) {
3813                             ZippyHoldings(white_holding, black_holding,
3814                                           new_piece);
3815                         }
3816 #endif /*ZIPPY*/
3817                         if (tinyLayout || smallLayout) {
3818                             char wh[16], bh[16];
3819                             PackHolding(wh, white_holding);
3820                             PackHolding(bh, black_holding);
3821                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3822                                     gameInfo.white, gameInfo.black);
3823                         } else {
3824                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3825                                     gameInfo.white, white_holding,
3826                                     gameInfo.black, black_holding);
3827                         }
3828                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3829                         DrawPosition(FALSE, boards[currentMove]);
3830                         DisplayTitle(str);
3831                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3832                         sscanf(parse, "game %d white [%s black [%s <- %s",
3833                                &gamenum, white_holding, black_holding,
3834                                new_piece);