Alternative sweep promotions
[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, savePiece = EmptySquare;
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 InitBackEnd1()
663 {
664     int matched, min, sec;
665
666     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
667     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
668
669     GetTimeMark(&programStartTime);
670     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
671
672     ClearProgramStats();
673     programStats.ok_to_send = 1;
674     programStats.seen_stat = 0;
675
676     /*
677      * Initialize game list
678      */
679     ListNew(&gameList);
680
681
682     /*
683      * Internet chess server status
684      */
685     if (appData.icsActive) {
686         appData.matchMode = FALSE;
687         appData.matchGames = 0;
688 #if ZIPPY
689         appData.noChessProgram = !appData.zippyPlay;
690 #else
691         appData.zippyPlay = FALSE;
692         appData.zippyTalk = FALSE;
693         appData.noChessProgram = TRUE;
694 #endif
695         if (*appData.icsHelper != NULLCHAR) {
696             appData.useTelnet = TRUE;
697             appData.telnetProgram = appData.icsHelper;
698         }
699     } else {
700         appData.zippyTalk = appData.zippyPlay = FALSE;
701     }
702
703     /* [AS] Initialize pv info list [HGM] and game state */
704     {
705         int i, j;
706
707         for( i=0; i<=framePtr; i++ ) {
708             pvInfoList[i].depth = -1;
709             boards[i][EP_STATUS] = EP_NONE;
710             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
711         }
712     }
713
714     /*
715      * Parse timeControl resource
716      */
717     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
718                           appData.movesPerSession)) {
719         char buf[MSG_SIZ];
720         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
721         DisplayFatalError(buf, 0, 2);
722     }
723
724     /*
725      * Parse searchTime resource
726      */
727     if (*appData.searchTime != NULLCHAR) {
728         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
729         if (matched == 1) {
730             searchTime = min * 60;
731         } else if (matched == 2) {
732             searchTime = min * 60 + sec;
733         } else {
734             char buf[MSG_SIZ];
735             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
736             DisplayFatalError(buf, 0, 2);
737         }
738     }
739
740     /* [AS] Adjudication threshold */
741     adjudicateLossThreshold = appData.adjudicateLossThreshold;
742
743     first.which = "first";
744     second.which = "second";
745     first.maybeThinking = second.maybeThinking = FALSE;
746     first.pr = second.pr = NoProc;
747     first.isr = second.isr = NULL;
748     first.sendTime = second.sendTime = 2;
749     first.sendDrawOffers = 1;
750     if (appData.firstPlaysBlack) {
751         first.twoMachinesColor = "black\n";
752         second.twoMachinesColor = "white\n";
753     } else {
754         first.twoMachinesColor = "white\n";
755         second.twoMachinesColor = "black\n";
756     }
757     first.program = appData.firstChessProgram;
758     second.program = appData.secondChessProgram;
759     first.host = appData.firstHost;
760     second.host = appData.secondHost;
761     first.dir = appData.firstDirectory;
762     second.dir = appData.secondDirectory;
763     first.other = &second;
764     second.other = &first;
765     first.initString = appData.initString;
766     second.initString = appData.secondInitString;
767     first.computerString = appData.firstComputerString;
768     second.computerString = appData.secondComputerString;
769     first.useSigint = second.useSigint = TRUE;
770     first.useSigterm = second.useSigterm = TRUE;
771     first.reuse = appData.reuseFirst;
772     second.reuse = appData.reuseSecond;
773     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
774     second.nps = appData.secondNPS;
775     first.useSetboard = second.useSetboard = FALSE;
776     first.useSAN = second.useSAN = FALSE;
777     first.usePing = second.usePing = FALSE;
778     first.lastPing = second.lastPing = 0;
779     first.lastPong = second.lastPong = 0;
780     first.usePlayother = second.usePlayother = FALSE;
781     first.useColors = second.useColors = TRUE;
782     first.useUsermove = second.useUsermove = FALSE;
783     first.sendICS = second.sendICS = FALSE;
784     first.sendName = second.sendName = appData.icsActive;
785     first.sdKludge = second.sdKludge = FALSE;
786     first.stKludge = second.stKludge = FALSE;
787     TidyProgramName(first.program, first.host, first.tidy);
788     TidyProgramName(second.program, second.host, second.tidy);
789     first.matchWins = second.matchWins = 0;
790     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
791     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
792     first.analysisSupport = second.analysisSupport = 2; /* detect */
793     first.analyzing = second.analyzing = FALSE;
794     first.initDone = second.initDone = FALSE;
795
796     /* New features added by Tord: */
797     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
798     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
799     /* End of new features added by Tord. */
800     first.fenOverride  = appData.fenOverride1;
801     second.fenOverride = appData.fenOverride2;
802
803     /* [HGM] time odds: set factor for each machine */
804     first.timeOdds  = appData.firstTimeOdds;
805     second.timeOdds = appData.secondTimeOdds;
806     { float norm = 1;
807         if(appData.timeOddsMode) {
808             norm = first.timeOdds;
809             if(norm > second.timeOdds) norm = second.timeOdds;
810         }
811         first.timeOdds /= norm;
812         second.timeOdds /= norm;
813     }
814
815     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
816     first.accumulateTC = appData.firstAccumulateTC;
817     second.accumulateTC = appData.secondAccumulateTC;
818     first.maxNrOfSessions = second.maxNrOfSessions = 1;
819
820     /* [HGM] debug */
821     first.debug = second.debug = FALSE;
822     first.supportsNPS = second.supportsNPS = UNKNOWN;
823
824     /* [HGM] options */
825     first.optionSettings  = appData.firstOptions;
826     second.optionSettings = appData.secondOptions;
827
828     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
829     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
830     first.isUCI = appData.firstIsUCI; /* [AS] */
831     second.isUCI = appData.secondIsUCI; /* [AS] */
832     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
833     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
834
835     if (appData.firstProtocolVersion > PROTOVER
836         || appData.firstProtocolVersion < 1)
837       {
838         char buf[MSG_SIZ];
839         int len;
840
841         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
842                        appData.firstProtocolVersion);
843         if( (len > MSG_SIZ) && appData.debugMode )
844           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
845
846         DisplayFatalError(buf, 0, 2);
847       }
848     else
849       {
850         first.protocolVersion = appData.firstProtocolVersion;
851       }
852
853     if (appData.secondProtocolVersion > PROTOVER
854         || appData.secondProtocolVersion < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.secondProtocolVersion);
861         if( (len > MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         second.protocolVersion = appData.secondProtocolVersion;
869       }
870
871     if (appData.icsActive) {
872         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
873 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
874     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
875         appData.clockMode = FALSE;
876         first.sendTime = second.sendTime = 0;
877     }
878
879 #if ZIPPY
880     /* Override some settings from environment variables, for backward
881        compatibility.  Unfortunately it's not feasible to have the env
882        vars just set defaults, at least in xboard.  Ugh.
883     */
884     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
885       ZippyInit();
886     }
887 #endif
888
889     if (appData.noChessProgram) {
890         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
891         sprintf(programVersion, "%s", PACKAGE_STRING);
892     } else {
893       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
894       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
895       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
896     }
897
898     if (!appData.icsActive) {
899       char buf[MSG_SIZ];
900       int len;
901
902       /* Check for variants that are supported only in ICS mode,
903          or not at all.  Some that are accepted here nevertheless
904          have bugs; see comments below.
905       */
906       VariantClass variant = StringToVariant(appData.variant);
907       switch (variant) {
908       case VariantBughouse:     /* need four players and two boards */
909       case VariantKriegspiel:   /* need to hide pieces and move details */
910         /* case VariantFischeRandom: (Fabien: moved below) */
911         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
912         if( (len > MSG_SIZ) && appData.debugMode )
913           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
914
915         DisplayFatalError(buf, 0, 2);
916         return;
917
918       case VariantUnknown:
919       case VariantLoadable:
920       case Variant29:
921       case Variant30:
922       case Variant31:
923       case Variant32:
924       case Variant33:
925       case Variant34:
926       case Variant35:
927       case Variant36:
928       default:
929         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
930         if( (len > MSG_SIZ) && appData.debugMode )
931           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
932
933         DisplayFatalError(buf, 0, 2);
934         return;
935
936       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
937       case VariantFairy:      /* [HGM] TestLegality definitely off! */
938       case VariantGothic:     /* [HGM] should work */
939       case VariantCapablanca: /* [HGM] should work */
940       case VariantCourier:    /* [HGM] initial forced moves not implemented */
941       case VariantShogi:      /* [HGM] could still mate with pawn drop */
942       case VariantKnightmate: /* [HGM] should work */
943       case VariantCylinder:   /* [HGM] untested */
944       case VariantFalcon:     /* [HGM] untested */
945       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
946                                  offboard interposition not understood */
947       case VariantNormal:     /* definitely works! */
948       case VariantWildCastle: /* pieces not automatically shuffled */
949       case VariantNoCastle:   /* pieces not automatically shuffled */
950       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
951       case VariantLosers:     /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantSuicide:    /* should work except for win condition,
954                                  and doesn't know captures are mandatory */
955       case VariantGiveaway:   /* should work except for win condition,
956                                  and doesn't know captures are mandatory */
957       case VariantTwoKings:   /* should work */
958       case VariantAtomic:     /* should work except for win condition */
959       case Variant3Check:     /* should work except for win condition */
960       case VariantShatranj:   /* should work except for all win conditions */
961       case VariantMakruk:     /* should work except for daw countdown */
962       case VariantBerolina:   /* might work if TestLegality is off */
963       case VariantCapaRandom: /* should work */
964       case VariantJanus:      /* should work */
965       case VariantSuper:      /* experimental */
966       case VariantGreat:      /* experimental, requires legality testing to be off */
967       case VariantSChess:     /* S-Chess, should work */
968       case VariantSpartan:    /* should work */
969         break;
970       }
971     }
972
973     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
974     InitEngineUCI( installDir, &second );
975 }
976
977 int NextIntegerFromString( char ** str, long * value )
978 {
979     int result = -1;
980     char * s = *str;
981
982     while( *s == ' ' || *s == '\t' ) {
983         s++;
984     }
985
986     *value = 0;
987
988     if( *s >= '0' && *s <= '9' ) {
989         while( *s >= '0' && *s <= '9' ) {
990             *value = *value * 10 + (*s - '0');
991             s++;
992         }
993
994         result = 0;
995     }
996
997     *str = s;
998
999     return result;
1000 }
1001
1002 int NextTimeControlFromString( char ** str, long * value )
1003 {
1004     long temp;
1005     int result = NextIntegerFromString( str, &temp );
1006
1007     if( result == 0 ) {
1008         *value = temp * 60; /* Minutes */
1009         if( **str == ':' ) {
1010             (*str)++;
1011             result = NextIntegerFromString( str, &temp );
1012             *value += temp; /* Seconds */
1013         }
1014     }
1015
1016     return result;
1017 }
1018
1019 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1020 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1021     int result = -1, type = 0; long temp, temp2;
1022
1023     if(**str != ':') return -1; // old params remain in force!
1024     (*str)++;
1025     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1026     if( NextIntegerFromString( str, &temp ) ) return -1;
1027     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1028
1029     if(**str != '/') {
1030         /* time only: incremental or sudden-death time control */
1031         if(**str == '+') { /* increment follows; read it */
1032             (*str)++;
1033             if(**str == '!') type = *(*str)++; // Bronstein TC
1034             if(result = NextIntegerFromString( str, &temp2)) return -1;
1035             *inc = temp2 * 1000;
1036             if(**str == '.') { // read fraction of increment
1037                 char *start = ++(*str);
1038                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1039                 temp2 *= 1000;
1040                 while(start++ < *str) temp2 /= 10;
1041                 *inc += temp2;
1042             }
1043         } else *inc = 0;
1044         *moves = 0; *tc = temp * 1000; *incType = type;
1045         return 0;
1046     }
1047
1048     (*str)++; /* classical time control */
1049     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1050
1051     if(result == 0) {
1052         *moves = temp;
1053         *tc    = temp2 * 1000;
1054         *inc   = 0;
1055         *incType = type;
1056     }
1057     return result;
1058 }
1059
1060 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1061 {   /* [HGM] get time to add from the multi-session time-control string */
1062     int incType, moves=1; /* kludge to force reading of first session */
1063     long time, increment;
1064     char *s = tcString;
1065
1066     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1067     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1068     do {
1069         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1070         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1071         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1072         if(movenr == -1) return time;    /* last move before new session     */
1073         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1074         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1075         if(!moves) return increment;     /* current session is incremental   */
1076         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1077     } while(movenr >= -1);               /* try again for next session       */
1078
1079     return 0; // no new time quota on this move
1080 }
1081
1082 int
1083 ParseTimeControl(tc, ti, mps)
1084      char *tc;
1085      float ti;
1086      int mps;
1087 {
1088   long tc1;
1089   long tc2;
1090   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1091   int min, sec=0;
1092
1093   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1094   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1095       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1096   if(ti > 0) {
1097
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1100     else 
1101       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1102   } else {
1103     if(mps)
1104       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1105     else 
1106       snprintf(buf, MSG_SIZ, ":%s", mytc);
1107   }
1108   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1109   
1110   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1111     return FALSE;
1112   }
1113
1114   if( *tc == '/' ) {
1115     /* Parse second time control */
1116     tc++;
1117
1118     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1119       return FALSE;
1120     }
1121
1122     if( tc2 == 0 ) {
1123       return FALSE;
1124     }
1125
1126     timeControl_2 = tc2 * 1000;
1127   }
1128   else {
1129     timeControl_2 = 0;
1130   }
1131
1132   if( tc1 == 0 ) {
1133     return FALSE;
1134   }
1135
1136   timeControl = tc1 * 1000;
1137
1138   if (ti >= 0) {
1139     timeIncrement = ti * 1000;  /* convert to ms */
1140     movesPerSession = 0;
1141   } else {
1142     timeIncrement = 0;
1143     movesPerSession = mps;
1144   }
1145   return TRUE;
1146 }
1147
1148 void
1149 InitBackEnd2()
1150 {
1151     if (appData.debugMode) {
1152         fprintf(debugFP, "%s\n", programVersion);
1153     }
1154
1155     set_cont_sequence(appData.wrapContSeq);
1156     if (appData.matchGames > 0) {
1157         appData.matchMode = TRUE;
1158     } else if (appData.matchMode) {
1159         appData.matchGames = 1;
1160     }
1161     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1162         appData.matchGames = appData.sameColorGames;
1163     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1164         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1165         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1166     }
1167     Reset(TRUE, FALSE);
1168     if (appData.noChessProgram || first.protocolVersion == 1) {
1169       InitBackEnd3();
1170     } else {
1171       /* kludge: allow timeout for initial "feature" commands */
1172       FreezeUI();
1173       DisplayMessage("", _("Starting chess program"));
1174       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1175     }
1176 }
1177
1178 void
1179 InitBackEnd3 P((void))
1180 {
1181     GameMode initialMode;
1182     char buf[MSG_SIZ];
1183     int err, len;
1184
1185     InitChessProgram(&first, startedFromSetupPosition);
1186
1187     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1188         free(programVersion);
1189         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1190         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1191     }
1192
1193     if (appData.icsActive) {
1194 #ifdef WIN32
1195         /* [DM] Make a console window if needed [HGM] merged ifs */
1196         ConsoleCreate();
1197 #endif
1198         err = establish();
1199         if (err != 0)
1200           {
1201             if (*appData.icsCommPort != NULLCHAR)
1202               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1203                              appData.icsCommPort);
1204             else
1205               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1206                         appData.icsHost, appData.icsPort);
1207
1208             if( (len > MSG_SIZ) && appData.debugMode )
1209               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1210
1211             DisplayFatalError(buf, err, 1);
1212             return;
1213         }
1214         SetICSMode();
1215         telnetISR =
1216           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1217         fromUserISR =
1218           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1219         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1220             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1221     } else if (appData.noChessProgram) {
1222         SetNCPMode();
1223     } else {
1224         SetGNUMode();
1225     }
1226
1227     if (*appData.cmailGameName != NULLCHAR) {
1228         SetCmailMode();
1229         OpenLoopback(&cmailPR);
1230         cmailISR =
1231           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1232     }
1233
1234     ThawUI();
1235     DisplayMessage("", "");
1236     if (StrCaseCmp(appData.initialMode, "") == 0) {
1237       initialMode = BeginningOfGame;
1238     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1239       initialMode = TwoMachinesPlay;
1240     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1241       initialMode = AnalyzeFile;
1242     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1243       initialMode = AnalyzeMode;
1244     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1245       initialMode = MachinePlaysWhite;
1246     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1247       initialMode = MachinePlaysBlack;
1248     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1249       initialMode = EditGame;
1250     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1251       initialMode = EditPosition;
1252     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1253       initialMode = Training;
1254     } else {
1255       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1256       if( (len > MSG_SIZ) && appData.debugMode )
1257         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1258
1259       DisplayFatalError(buf, 0, 2);
1260       return;
1261     }
1262
1263     if (appData.matchMode) {
1264         /* Set up machine vs. machine match */
1265         if (appData.noChessProgram) {
1266             DisplayFatalError(_("Can't have a match with no chess programs"),
1267                               0, 2);
1268             return;
1269         }
1270         matchMode = TRUE;
1271         matchGame = 1;
1272         if (*appData.loadGameFile != NULLCHAR) {
1273             int index = appData.loadGameIndex; // [HGM] autoinc
1274             if(index<0) lastIndex = index = 1;
1275             if (!LoadGameFromFile(appData.loadGameFile,
1276                                   index,
1277                                   appData.loadGameFile, FALSE)) {
1278                 DisplayFatalError(_("Bad game file"), 0, 1);
1279                 return;
1280             }
1281         } else if (*appData.loadPositionFile != NULLCHAR) {
1282             int index = appData.loadPositionIndex; // [HGM] autoinc
1283             if(index<0) lastIndex = index = 1;
1284             if (!LoadPositionFromFile(appData.loadPositionFile,
1285                                       index,
1286                                       appData.loadPositionFile)) {
1287                 DisplayFatalError(_("Bad position file"), 0, 1);
1288                 return;
1289             }
1290         }
1291         TwoMachinesEvent();
1292     } else if (*appData.cmailGameName != NULLCHAR) {
1293         /* Set up cmail mode */
1294         ReloadCmailMsgEvent(TRUE);
1295     } else {
1296         /* Set up other modes */
1297         if (initialMode == AnalyzeFile) {
1298           if (*appData.loadGameFile == NULLCHAR) {
1299             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1300             return;
1301           }
1302         }
1303         if (*appData.loadGameFile != NULLCHAR) {
1304             (void) LoadGameFromFile(appData.loadGameFile,
1305                                     appData.loadGameIndex,
1306                                     appData.loadGameFile, TRUE);
1307         } else if (*appData.loadPositionFile != NULLCHAR) {
1308             (void) LoadPositionFromFile(appData.loadPositionFile,
1309                                         appData.loadPositionIndex,
1310                                         appData.loadPositionFile);
1311             /* [HGM] try to make self-starting even after FEN load */
1312             /* to allow automatic setup of fairy variants with wtm */
1313             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1314                 gameMode = BeginningOfGame;
1315                 setboardSpoiledMachineBlack = 1;
1316             }
1317             /* [HGM] loadPos: make that every new game uses the setup */
1318             /* from file as long as we do not switch variant          */
1319             if(!blackPlaysFirst) {
1320                 startedFromPositionFile = TRUE;
1321                 CopyBoard(filePosition, boards[0]);
1322             }
1323         }
1324         if (initialMode == AnalyzeMode) {
1325           if (appData.noChessProgram) {
1326             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1327             return;
1328           }
1329           if (appData.icsActive) {
1330             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1331             return;
1332           }
1333           AnalyzeModeEvent();
1334         } else if (initialMode == AnalyzeFile) {
1335           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1336           ShowThinkingEvent();
1337           AnalyzeFileEvent();
1338           AnalysisPeriodicEvent(1);
1339         } else if (initialMode == MachinePlaysWhite) {
1340           if (appData.noChessProgram) {
1341             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1342                               0, 2);
1343             return;
1344           }
1345           if (appData.icsActive) {
1346             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1347                               0, 2);
1348             return;
1349           }
1350           MachineWhiteEvent();
1351         } else if (initialMode == MachinePlaysBlack) {
1352           if (appData.noChessProgram) {
1353             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1354                               0, 2);
1355             return;
1356           }
1357           if (appData.icsActive) {
1358             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1359                               0, 2);
1360             return;
1361           }
1362           MachineBlackEvent();
1363         } else if (initialMode == TwoMachinesPlay) {
1364           if (appData.noChessProgram) {
1365             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1366                               0, 2);
1367             return;
1368           }
1369           if (appData.icsActive) {
1370             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1371                               0, 2);
1372             return;
1373           }
1374           TwoMachinesEvent();
1375         } else if (initialMode == EditGame) {
1376           EditGameEvent();
1377         } else if (initialMode == EditPosition) {
1378           EditPositionEvent();
1379         } else if (initialMode == Training) {
1380           if (*appData.loadGameFile == NULLCHAR) {
1381             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1382             return;
1383           }
1384           TrainingEvent();
1385         }
1386     }
1387 }
1388
1389 /*
1390  * Establish will establish a contact to a remote host.port.
1391  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1392  *  used to talk to the host.
1393  * Returns 0 if okay, error code if not.
1394  */
1395 int
1396 establish()
1397 {
1398     char buf[MSG_SIZ];
1399
1400     if (*appData.icsCommPort != NULLCHAR) {
1401         /* Talk to the host through a serial comm port */
1402         return OpenCommPort(appData.icsCommPort, &icsPR);
1403
1404     } else if (*appData.gateway != NULLCHAR) {
1405         if (*appData.remoteShell == NULLCHAR) {
1406             /* Use the rcmd protocol to run telnet program on a gateway host */
1407             snprintf(buf, sizeof(buf), "%s %s %s",
1408                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1409             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1410
1411         } else {
1412             /* Use the rsh program to run telnet program on a gateway host */
1413             if (*appData.remoteUser == NULLCHAR) {
1414                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1415                         appData.gateway, appData.telnetProgram,
1416                         appData.icsHost, appData.icsPort);
1417             } else {
1418                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1419                         appData.remoteShell, appData.gateway,
1420                         appData.remoteUser, appData.telnetProgram,
1421                         appData.icsHost, appData.icsPort);
1422             }
1423             return StartChildProcess(buf, "", &icsPR);
1424
1425         }
1426     } else if (appData.useTelnet) {
1427         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1428
1429     } else {
1430         /* TCP socket interface differs somewhat between
1431            Unix and NT; handle details in the front end.
1432            */
1433         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1434     }
1435 }
1436
1437 void EscapeExpand(char *p, char *q)
1438 {       // [HGM] initstring: routine to shape up string arguments
1439         while(*p++ = *q++) if(p[-1] == '\\')
1440             switch(*q++) {
1441                 case 'n': p[-1] = '\n'; break;
1442                 case 'r': p[-1] = '\r'; break;
1443                 case 't': p[-1] = '\t'; break;
1444                 case '\\': p[-1] = '\\'; break;
1445                 case 0: *p = 0; return;
1446                 default: p[-1] = q[-1]; break;
1447             }
1448 }
1449
1450 void
1451 show_bytes(fp, buf, count)
1452      FILE *fp;
1453      char *buf;
1454      int count;
1455 {
1456     while (count--) {
1457         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1458             fprintf(fp, "\\%03o", *buf & 0xff);
1459         } else {
1460             putc(*buf, fp);
1461         }
1462         buf++;
1463     }
1464     fflush(fp);
1465 }
1466
1467 /* Returns an errno value */
1468 int
1469 OutputMaybeTelnet(pr, message, count, outError)
1470      ProcRef pr;
1471      char *message;
1472      int count;
1473      int *outError;
1474 {
1475     char buf[8192], *p, *q, *buflim;
1476     int left, newcount, outcount;
1477
1478     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1479         *appData.gateway != NULLCHAR) {
1480         if (appData.debugMode) {
1481             fprintf(debugFP, ">ICS: ");
1482             show_bytes(debugFP, message, count);
1483             fprintf(debugFP, "\n");
1484         }
1485         return OutputToProcess(pr, message, count, outError);
1486     }
1487
1488     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1489     p = message;
1490     q = buf;
1491     left = count;
1492     newcount = 0;
1493     while (left) {
1494         if (q >= buflim) {
1495             if (appData.debugMode) {
1496                 fprintf(debugFP, ">ICS: ");
1497                 show_bytes(debugFP, buf, newcount);
1498                 fprintf(debugFP, "\n");
1499             }
1500             outcount = OutputToProcess(pr, buf, newcount, outError);
1501             if (outcount < newcount) return -1; /* to be sure */
1502             q = buf;
1503             newcount = 0;
1504         }
1505         if (*p == '\n') {
1506             *q++ = '\r';
1507             newcount++;
1508         } else if (((unsigned char) *p) == TN_IAC) {
1509             *q++ = (char) TN_IAC;
1510             newcount ++;
1511         }
1512         *q++ = *p++;
1513         newcount++;
1514         left--;
1515     }
1516     if (appData.debugMode) {
1517         fprintf(debugFP, ">ICS: ");
1518         show_bytes(debugFP, buf, newcount);
1519         fprintf(debugFP, "\n");
1520     }
1521     outcount = OutputToProcess(pr, buf, newcount, outError);
1522     if (outcount < newcount) return -1; /* to be sure */
1523     return count;
1524 }
1525
1526 void
1527 read_from_player(isr, closure, message, count, error)
1528      InputSourceRef isr;
1529      VOIDSTAR closure;
1530      char *message;
1531      int count;
1532      int error;
1533 {
1534     int outError, outCount;
1535     static int gotEof = 0;
1536
1537     /* Pass data read from player on to ICS */
1538     if (count > 0) {
1539         gotEof = 0;
1540         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1541         if (outCount < count) {
1542             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1543         }
1544     } else if (count < 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1547     } else if (gotEof++ > 0) {
1548         RemoveInputSource(isr);
1549         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1550     }
1551 }
1552
1553 void
1554 KeepAlive()
1555 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1556     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1557     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1558     SendToICS("date\n");
1559     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1560 }
1561
1562 /* added routine for printf style output to ics */
1563 void ics_printf(char *format, ...)
1564 {
1565     char buffer[MSG_SIZ];
1566     va_list args;
1567
1568     va_start(args, format);
1569     vsnprintf(buffer, sizeof(buffer), format, args);
1570     buffer[sizeof(buffer)-1] = '\0';
1571     SendToICS(buffer);
1572     va_end(args);
1573 }
1574
1575 void
1576 SendToICS(s)
1577      char *s;
1578 {
1579     int count, outCount, outError;
1580
1581     if (icsPR == NULL) return;
1582
1583     count = strlen(s);
1584     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1585     if (outCount < count) {
1586         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1587     }
1588 }
1589
1590 /* This is used for sending logon scripts to the ICS. Sending
1591    without a delay causes problems when using timestamp on ICC
1592    (at least on my machine). */
1593 void
1594 SendToICSDelayed(s,msdelay)
1595      char *s;
1596      long msdelay;
1597 {
1598     int count, outCount, outError;
1599
1600     if (icsPR == NULL) return;
1601
1602     count = strlen(s);
1603     if (appData.debugMode) {
1604         fprintf(debugFP, ">ICS: ");
1605         show_bytes(debugFP, s, count);
1606         fprintf(debugFP, "\n");
1607     }
1608     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1609                                       msdelay);
1610     if (outCount < count) {
1611         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1612     }
1613 }
1614
1615
1616 /* Remove all highlighting escape sequences in s
1617    Also deletes any suffix starting with '('
1618    */
1619 char *
1620 StripHighlightAndTitle(s)
1621      char *s;
1622 {
1623     static char retbuf[MSG_SIZ];
1624     char *p = retbuf;
1625
1626     while (*s != NULLCHAR) {
1627         while (*s == '\033') {
1628             while (*s != NULLCHAR && !isalpha(*s)) s++;
1629             if (*s != NULLCHAR) s++;
1630         }
1631         while (*s != NULLCHAR && *s != '\033') {
1632             if (*s == '(' || *s == '[') {
1633                 *p = NULLCHAR;
1634                 return retbuf;
1635             }
1636             *p++ = *s++;
1637         }
1638     }
1639     *p = NULLCHAR;
1640     return retbuf;
1641 }
1642
1643 /* Remove all highlighting escape sequences in s */
1644 char *
1645 StripHighlight(s)
1646      char *s;
1647 {
1648     static char retbuf[MSG_SIZ];
1649     char *p = retbuf;
1650
1651     while (*s != NULLCHAR) {
1652         while (*s == '\033') {
1653             while (*s != NULLCHAR && !isalpha(*s)) s++;
1654             if (*s != NULLCHAR) s++;
1655         }
1656         while (*s != NULLCHAR && *s != '\033') {
1657             *p++ = *s++;
1658         }
1659     }
1660     *p = NULLCHAR;
1661     return retbuf;
1662 }
1663
1664 char *variantNames[] = VARIANT_NAMES;
1665 char *
1666 VariantName(v)
1667      VariantClass v;
1668 {
1669     return variantNames[v];
1670 }
1671
1672
1673 /* Identify a variant from the strings the chess servers use or the
1674    PGN Variant tag names we use. */
1675 VariantClass
1676 StringToVariant(e)
1677      char *e;
1678 {
1679     char *p;
1680     int wnum = -1;
1681     VariantClass v = VariantNormal;
1682     int i, found = FALSE;
1683     char buf[MSG_SIZ];
1684     int len;
1685
1686     if (!e) return v;
1687
1688     /* [HGM] skip over optional board-size prefixes */
1689     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1690         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1691         while( *e++ != '_');
1692     }
1693
1694     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1695         v = VariantNormal;
1696         found = TRUE;
1697     } else
1698     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1699       if (StrCaseStr(e, variantNames[i])) {
1700         v = (VariantClass) i;
1701         found = TRUE;
1702         break;
1703       }
1704     }
1705
1706     if (!found) {
1707       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1708           || StrCaseStr(e, "wild/fr")
1709           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1710         v = VariantFischeRandom;
1711       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1712                  (i = 1, p = StrCaseStr(e, "w"))) {
1713         p += i;
1714         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1715         if (isdigit(*p)) {
1716           wnum = atoi(p);
1717         } else {
1718           wnum = -1;
1719         }
1720         switch (wnum) {
1721         case 0: /* FICS only, actually */
1722         case 1:
1723           /* Castling legal even if K starts on d-file */
1724           v = VariantWildCastle;
1725           break;
1726         case 2:
1727         case 3:
1728         case 4:
1729           /* Castling illegal even if K & R happen to start in
1730              normal positions. */
1731           v = VariantNoCastle;
1732           break;
1733         case 5:
1734         case 7:
1735         case 8:
1736         case 10:
1737         case 11:
1738         case 12:
1739         case 13:
1740         case 14:
1741         case 15:
1742         case 18:
1743         case 19:
1744           /* Castling legal iff K & R start in normal positions */
1745           v = VariantNormal;
1746           break;
1747         case 6:
1748         case 20:
1749         case 21:
1750           /* Special wilds for position setup; unclear what to do here */
1751           v = VariantLoadable;
1752           break;
1753         case 9:
1754           /* Bizarre ICC game */
1755           v = VariantTwoKings;
1756           break;
1757         case 16:
1758           v = VariantKriegspiel;
1759           break;
1760         case 17:
1761           v = VariantLosers;
1762           break;
1763         case 22:
1764           v = VariantFischeRandom;
1765           break;
1766         case 23:
1767           v = VariantCrazyhouse;
1768           break;
1769         case 24:
1770           v = VariantBughouse;
1771           break;
1772         case 25:
1773           v = Variant3Check;
1774           break;
1775         case 26:
1776           /* Not quite the same as FICS suicide! */
1777           v = VariantGiveaway;
1778           break;
1779         case 27:
1780           v = VariantAtomic;
1781           break;
1782         case 28:
1783           v = VariantShatranj;
1784           break;
1785
1786         /* Temporary names for future ICC types.  The name *will* change in
1787            the next xboard/WinBoard release after ICC defines it. */
1788         case 29:
1789           v = Variant29;
1790           break;
1791         case 30:
1792           v = Variant30;
1793           break;
1794         case 31:
1795           v = Variant31;
1796           break;
1797         case 32:
1798           v = Variant32;
1799           break;
1800         case 33:
1801           v = Variant33;
1802           break;
1803         case 34:
1804           v = Variant34;
1805           break;
1806         case 35:
1807           v = Variant35;
1808           break;
1809         case 36:
1810           v = Variant36;
1811           break;
1812         case 37:
1813           v = VariantShogi;
1814           break;
1815         case 38:
1816           v = VariantXiangqi;
1817           break;
1818         case 39:
1819           v = VariantCourier;
1820           break;
1821         case 40:
1822           v = VariantGothic;
1823           break;
1824         case 41:
1825           v = VariantCapablanca;
1826           break;
1827         case 42:
1828           v = VariantKnightmate;
1829           break;
1830         case 43:
1831           v = VariantFairy;
1832           break;
1833         case 44:
1834           v = VariantCylinder;
1835           break;
1836         case 45:
1837           v = VariantFalcon;
1838           break;
1839         case 46:
1840           v = VariantCapaRandom;
1841           break;
1842         case 47:
1843           v = VariantBerolina;
1844           break;
1845         case 48:
1846           v = VariantJanus;
1847           break;
1848         case 49:
1849           v = VariantSuper;
1850           break;
1851         case 50:
1852           v = VariantGreat;
1853           break;
1854         case -1:
1855           /* Found "wild" or "w" in the string but no number;
1856              must assume it's normal chess. */
1857           v = VariantNormal;
1858           break;
1859         default:
1860           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1861           if( (len > MSG_SIZ) && appData.debugMode )
1862             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1863
1864           DisplayError(buf, 0);
1865           v = VariantUnknown;
1866           break;
1867         }
1868       }
1869     }
1870     if (appData.debugMode) {
1871       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1872               e, wnum, VariantName(v));
1873     }
1874     return v;
1875 }
1876
1877 static int leftover_start = 0, leftover_len = 0;
1878 char star_match[STAR_MATCH_N][MSG_SIZ];
1879
1880 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1881    advance *index beyond it, and set leftover_start to the new value of
1882    *index; else return FALSE.  If pattern contains the character '*', it
1883    matches any sequence of characters not containing '\r', '\n', or the
1884    character following the '*' (if any), and the matched sequence(s) are
1885    copied into star_match.
1886    */
1887 int
1888 looking_at(buf, index, pattern)
1889      char *buf;
1890      int *index;
1891      char *pattern;
1892 {
1893     char *bufp = &buf[*index], *patternp = pattern;
1894     int star_count = 0;
1895     char *matchp = star_match[0];
1896
1897     for (;;) {
1898         if (*patternp == NULLCHAR) {
1899             *index = leftover_start = bufp - buf;
1900             *matchp = NULLCHAR;
1901             return TRUE;
1902         }
1903         if (*bufp == NULLCHAR) return FALSE;
1904         if (*patternp == '*') {
1905             if (*bufp == *(patternp + 1)) {
1906                 *matchp = NULLCHAR;
1907                 matchp = star_match[++star_count];
1908                 patternp += 2;
1909                 bufp++;
1910                 continue;
1911             } else if (*bufp == '\n' || *bufp == '\r') {
1912                 patternp++;
1913                 if (*patternp == NULLCHAR)
1914                   continue;
1915                 else
1916                   return FALSE;
1917             } else {
1918                 *matchp++ = *bufp++;
1919                 continue;
1920             }
1921         }
1922         if (*patternp != *bufp) return FALSE;
1923         patternp++;
1924         bufp++;
1925     }
1926 }
1927
1928 void
1929 SendToPlayer(data, length)
1930      char *data;
1931      int length;
1932 {
1933     int error, outCount;
1934     outCount = OutputToProcess(NoProc, data, length, &error);
1935     if (outCount < length) {
1936         DisplayFatalError(_("Error writing to display"), error, 1);
1937     }
1938 }
1939
1940 void
1941 PackHolding(packed, holding)
1942      char packed[];
1943      char *holding;
1944 {
1945     char *p = holding;
1946     char *q = packed;
1947     int runlength = 0;
1948     int curr = 9999;
1949     do {
1950         if (*p == curr) {
1951             runlength++;
1952         } else {
1953             switch (runlength) {
1954               case 0:
1955                 break;
1956               case 1:
1957                 *q++ = curr;
1958                 break;
1959               case 2:
1960                 *q++ = curr;
1961                 *q++ = curr;
1962                 break;
1963               default:
1964                 sprintf(q, "%d", runlength);
1965                 while (*q) q++;
1966                 *q++ = curr;
1967                 break;
1968             }
1969             runlength = 1;
1970             curr = *p;
1971         }
1972     } while (*p++);
1973     *q = NULLCHAR;
1974 }
1975
1976 /* Telnet protocol requests from the front end */
1977 void
1978 TelnetRequest(ddww, option)
1979      unsigned char ddww, option;
1980 {
1981     unsigned char msg[3];
1982     int outCount, outError;
1983
1984     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1985
1986     if (appData.debugMode) {
1987         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1988         switch (ddww) {
1989           case TN_DO:
1990             ddwwStr = "DO";
1991             break;
1992           case TN_DONT:
1993             ddwwStr = "DONT";
1994             break;
1995           case TN_WILL:
1996             ddwwStr = "WILL";
1997             break;
1998           case TN_WONT:
1999             ddwwStr = "WONT";
2000             break;
2001           default:
2002             ddwwStr = buf1;
2003             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2004             break;
2005         }
2006         switch (option) {
2007           case TN_ECHO:
2008             optionStr = "ECHO";
2009             break;
2010           default:
2011             optionStr = buf2;
2012             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2013             break;
2014         }
2015         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2016     }
2017     msg[0] = TN_IAC;
2018     msg[1] = ddww;
2019     msg[2] = option;
2020     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2021     if (outCount < 3) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 void
2027 DoEcho()
2028 {
2029     if (!appData.icsActive) return;
2030     TelnetRequest(TN_DO, TN_ECHO);
2031 }
2032
2033 void
2034 DontEcho()
2035 {
2036     if (!appData.icsActive) return;
2037     TelnetRequest(TN_DONT, TN_ECHO);
2038 }
2039
2040 void
2041 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2042 {
2043     /* put the holdings sent to us by the server on the board holdings area */
2044     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2045     char p;
2046     ChessSquare piece;
2047
2048     if(gameInfo.holdingsWidth < 2)  return;
2049     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2050         return; // prevent overwriting by pre-board holdings
2051
2052     if( (int)lowestPiece >= BlackPawn ) {
2053         holdingsColumn = 0;
2054         countsColumn = 1;
2055         holdingsStartRow = BOARD_HEIGHT-1;
2056         direction = -1;
2057     } else {
2058         holdingsColumn = BOARD_WIDTH-1;
2059         countsColumn = BOARD_WIDTH-2;
2060         holdingsStartRow = 0;
2061         direction = 1;
2062     }
2063
2064     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2065         board[i][holdingsColumn] = EmptySquare;
2066         board[i][countsColumn]   = (ChessSquare) 0;
2067     }
2068     while( (p=*holdings++) != NULLCHAR ) {
2069         piece = CharToPiece( ToUpper(p) );
2070         if(piece == EmptySquare) continue;
2071         /*j = (int) piece - (int) WhitePawn;*/
2072         j = PieceToNumber(piece);
2073         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2074         if(j < 0) continue;               /* should not happen */
2075         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2076         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2077         board[holdingsStartRow+j*direction][countsColumn]++;
2078     }
2079 }
2080
2081
2082 void
2083 VariantSwitch(Board board, VariantClass newVariant)
2084 {
2085    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2086    static Board oldBoard;
2087
2088    startedFromPositionFile = FALSE;
2089    if(gameInfo.variant == newVariant) return;
2090
2091    /* [HGM] This routine is called each time an assignment is made to
2092     * gameInfo.variant during a game, to make sure the board sizes
2093     * are set to match the new variant. If that means adding or deleting
2094     * holdings, we shift the playing board accordingly
2095     * This kludge is needed because in ICS observe mode, we get boards
2096     * of an ongoing game without knowing the variant, and learn about the
2097     * latter only later. This can be because of the move list we requested,
2098     * in which case the game history is refilled from the beginning anyway,
2099     * but also when receiving holdings of a crazyhouse game. In the latter
2100     * case we want to add those holdings to the already received position.
2101     */
2102
2103
2104    if (appData.debugMode) {
2105      fprintf(debugFP, "Switch board from %s to %s\n",
2106              VariantName(gameInfo.variant), VariantName(newVariant));
2107      setbuf(debugFP, NULL);
2108    }
2109    shuffleOpenings = 0;       /* [HGM] shuffle */
2110    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2111    switch(newVariant)
2112      {
2113      case VariantShogi:
2114        newWidth = 9;  newHeight = 9;
2115        gameInfo.holdingsSize = 7;
2116      case VariantBughouse:
2117      case VariantCrazyhouse:
2118        newHoldingsWidth = 2; break;
2119      case VariantGreat:
2120        newWidth = 10;
2121      case VariantSuper:
2122        newHoldingsWidth = 2;
2123        gameInfo.holdingsSize = 8;
2124        break;
2125      case VariantGothic:
2126      case VariantCapablanca:
2127      case VariantCapaRandom:
2128        newWidth = 10;
2129      default:
2130        newHoldingsWidth = gameInfo.holdingsSize = 0;
2131      };
2132
2133    if(newWidth  != gameInfo.boardWidth  ||
2134       newHeight != gameInfo.boardHeight ||
2135       newHoldingsWidth != gameInfo.holdingsWidth ) {
2136
2137      /* shift position to new playing area, if needed */
2138      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2139        for(i=0; i<BOARD_HEIGHT; i++)
2140          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2141            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142              board[i][j];
2143        for(i=0; i<newHeight; i++) {
2144          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2145          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2146        }
2147      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2148        for(i=0; i<BOARD_HEIGHT; i++)
2149          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2150            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2151              board[i][j];
2152      }
2153      gameInfo.boardWidth  = newWidth;
2154      gameInfo.boardHeight = newHeight;
2155      gameInfo.holdingsWidth = newHoldingsWidth;
2156      gameInfo.variant = newVariant;
2157      InitDrawingSizes(-2, 0);
2158    } else gameInfo.variant = newVariant;
2159    CopyBoard(oldBoard, board);   // remember correctly formatted board
2160      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2161    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2162 }
2163
2164 static int loggedOn = FALSE;
2165
2166 /*-- Game start info cache: --*/
2167 int gs_gamenum;
2168 char gs_kind[MSG_SIZ];
2169 static char player1Name[128] = "";
2170 static char player2Name[128] = "";
2171 static char cont_seq[] = "\n\\   ";
2172 static int player1Rating = -1;
2173 static int player2Rating = -1;
2174 /*----------------------------*/
2175
2176 ColorClass curColor = ColorNormal;
2177 int suppressKibitz = 0;
2178
2179 // [HGM] seekgraph
2180 Boolean soughtPending = FALSE;
2181 Boolean seekGraphUp;
2182 #define MAX_SEEK_ADS 200
2183 #define SQUARE 0x80
2184 char *seekAdList[MAX_SEEK_ADS];
2185 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2186 float tcList[MAX_SEEK_ADS];
2187 char colorList[MAX_SEEK_ADS];
2188 int nrOfSeekAds = 0;
2189 int minRating = 1010, maxRating = 2800;
2190 int hMargin = 10, vMargin = 20, h, w;
2191 extern int squareSize, lineGap;
2192
2193 void
2194 PlotSeekAd(int i)
2195 {
2196         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2197         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2198         if(r < minRating+100 && r >=0 ) r = minRating+100;
2199         if(r > maxRating) r = maxRating;
2200         if(tc < 1.) tc = 1.;
2201         if(tc > 95.) tc = 95.;
2202         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2203         y = ((double)r - minRating)/(maxRating - minRating)
2204             * (h-vMargin-squareSize/8-1) + vMargin;
2205         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2206         if(strstr(seekAdList[i], " u ")) color = 1;
2207         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2208            !strstr(seekAdList[i], "bullet") &&
2209            !strstr(seekAdList[i], "blitz") &&
2210            !strstr(seekAdList[i], "standard") ) color = 2;
2211         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2212         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2213 }
2214
2215 void
2216 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2217 {
2218         char buf[MSG_SIZ], *ext = "";
2219         VariantClass v = StringToVariant(type);
2220         if(strstr(type, "wild")) {
2221             ext = type + 4; // append wild number
2222             if(v == VariantFischeRandom) type = "chess960"; else
2223             if(v == VariantLoadable) type = "setup"; else
2224             type = VariantName(v);
2225         }
2226         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2227         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2228             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2229             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2230             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2231             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2232             seekNrList[nrOfSeekAds] = nr;
2233             zList[nrOfSeekAds] = 0;
2234             seekAdList[nrOfSeekAds++] = StrSave(buf);
2235             if(plot) PlotSeekAd(nrOfSeekAds-1);
2236         }
2237 }
2238
2239 void
2240 EraseSeekDot(int i)
2241 {
2242     int x = xList[i], y = yList[i], d=squareSize/4, k;
2243     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2244     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2245     // now replot every dot that overlapped
2246     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2247         int xx = xList[k], yy = yList[k];
2248         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2249             DrawSeekDot(xx, yy, colorList[k]);
2250     }
2251 }
2252
2253 void
2254 RemoveSeekAd(int nr)
2255 {
2256         int i;
2257         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2258             EraseSeekDot(i);
2259             if(seekAdList[i]) free(seekAdList[i]);
2260             seekAdList[i] = seekAdList[--nrOfSeekAds];
2261             seekNrList[i] = seekNrList[nrOfSeekAds];
2262             ratingList[i] = ratingList[nrOfSeekAds];
2263             colorList[i]  = colorList[nrOfSeekAds];
2264             tcList[i] = tcList[nrOfSeekAds];
2265             xList[i]  = xList[nrOfSeekAds];
2266             yList[i]  = yList[nrOfSeekAds];
2267             zList[i]  = zList[nrOfSeekAds];
2268             seekAdList[nrOfSeekAds] = NULL;
2269             break;
2270         }
2271 }
2272
2273 Boolean
2274 MatchSoughtLine(char *line)
2275 {
2276     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2277     int nr, base, inc, u=0; char dummy;
2278
2279     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2281        (u=1) &&
2282        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2283         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2284         // match: compact and save the line
2285         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2286         return TRUE;
2287     }
2288     return FALSE;
2289 }
2290
2291 int
2292 DrawSeekGraph()
2293 {
2294     int i;
2295     if(!seekGraphUp) return FALSE;
2296     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2297     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2298
2299     DrawSeekBackground(0, 0, w, h);
2300     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2301     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2302     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2303         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2304         yy = h-1-yy;
2305         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2306         if(i%500 == 0) {
2307             char buf[MSG_SIZ];
2308             snprintf(buf, MSG_SIZ, "%d", i);
2309             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2310         }
2311     }
2312     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2313     for(i=1; i<100; i+=(i<10?1:5)) {
2314         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2315         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2316         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2317             char buf[MSG_SIZ];
2318             snprintf(buf, MSG_SIZ, "%d", i);
2319             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2320         }
2321     }
2322     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2323     return TRUE;
2324 }
2325
2326 int SeekGraphClick(ClickType click, int x, int y, int moving)
2327 {
2328     static int lastDown = 0, displayed = 0, lastSecond;
2329     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2330         if(click == Release || moving) return FALSE;
2331         nrOfSeekAds = 0;
2332         soughtPending = TRUE;
2333         SendToICS(ics_prefix);
2334         SendToICS("sought\n"); // should this be "sought all"?
2335     } else { // issue challenge based on clicked ad
2336         int dist = 10000; int i, closest = 0, second = 0;
2337         for(i=0; i<nrOfSeekAds; i++) {
2338             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2339             if(d < dist) { dist = d; closest = i; }
2340             second += (d - zList[i] < 120); // count in-range ads
2341             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2342         }
2343         if(dist < 120) {
2344             char buf[MSG_SIZ];
2345             second = (second > 1);
2346             if(displayed != closest || second != lastSecond) {
2347                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2348                 lastSecond = second; displayed = closest;
2349             }
2350             if(click == Press) {
2351                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2352                 lastDown = closest;
2353                 return TRUE;
2354             } // on press 'hit', only show info
2355             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2356             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2357             SendToICS(ics_prefix);
2358             SendToICS(buf);
2359             return TRUE; // let incoming board of started game pop down the graph
2360         } else if(click == Release) { // release 'miss' is ignored
2361             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2362             if(moving == 2) { // right up-click
2363                 nrOfSeekAds = 0; // refresh graph
2364                 soughtPending = TRUE;
2365                 SendToICS(ics_prefix);
2366                 SendToICS("sought\n"); // should this be "sought all"?
2367             }
2368             return TRUE;
2369         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2370         // press miss or release hit 'pop down' seek graph
2371         seekGraphUp = FALSE;
2372         DrawPosition(TRUE, NULL);
2373     }
2374     return TRUE;
2375 }
2376
2377 void
2378 read_from_ics(isr, closure, data, count, error)
2379      InputSourceRef isr;
2380      VOIDSTAR closure;
2381      char *data;
2382      int count;
2383      int error;
2384 {
2385 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2386 #define STARTED_NONE 0
2387 #define STARTED_MOVES 1
2388 #define STARTED_BOARD 2
2389 #define STARTED_OBSERVE 3
2390 #define STARTED_HOLDINGS 4
2391 #define STARTED_CHATTER 5
2392 #define STARTED_COMMENT 6
2393 #define STARTED_MOVES_NOHIDE 7
2394
2395     static int started = STARTED_NONE;
2396     static char parse[20000];
2397     static int parse_pos = 0;
2398     static char buf[BUF_SIZE + 1];
2399     static int firstTime = TRUE, intfSet = FALSE;
2400     static ColorClass prevColor = ColorNormal;
2401     static int savingComment = FALSE;
2402     static int cmatch = 0; // continuation sequence match
2403     char *bp;
2404     char str[MSG_SIZ];
2405     int i, oldi;
2406     int buf_len;
2407     int next_out;
2408     int tkind;
2409     int backup;    /* [DM] For zippy color lines */
2410     char *p;
2411     char talker[MSG_SIZ]; // [HGM] chat
2412     int channel;
2413
2414     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2415
2416     if (appData.debugMode) {
2417       if (!error) {
2418         fprintf(debugFP, "<ICS: ");
2419         show_bytes(debugFP, data, count);
2420         fprintf(debugFP, "\n");
2421       }
2422     }
2423
2424     if (appData.debugMode) { int f = forwardMostMove;
2425         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2426                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2427                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2428     }
2429     if (count > 0) {
2430         /* If last read ended with a partial line that we couldn't parse,
2431            prepend it to the new read and try again. */
2432         if (leftover_len > 0) {
2433             for (i=0; i<leftover_len; i++)
2434               buf[i] = buf[leftover_start + i];
2435         }
2436
2437     /* copy new characters into the buffer */
2438     bp = buf + leftover_len;
2439     buf_len=leftover_len;
2440     for (i=0; i<count; i++)
2441     {
2442         // ignore these
2443         if (data[i] == '\r')
2444             continue;
2445
2446         // join lines split by ICS?
2447         if (!appData.noJoin)
2448         {
2449             /*
2450                 Joining just consists of finding matches against the
2451                 continuation sequence, and discarding that sequence
2452                 if found instead of copying it.  So, until a match
2453                 fails, there's nothing to do since it might be the
2454                 complete sequence, and thus, something we don't want
2455                 copied.
2456             */
2457             if (data[i] == cont_seq[cmatch])
2458             {
2459                 cmatch++;
2460                 if (cmatch == strlen(cont_seq))
2461                 {
2462                     cmatch = 0; // complete match.  just reset the counter
2463
2464                     /*
2465                         it's possible for the ICS to not include the space
2466                         at the end of the last word, making our [correct]
2467                         join operation fuse two separate words.  the server
2468                         does this when the space occurs at the width setting.
2469                     */
2470                     if (!buf_len || buf[buf_len-1] != ' ')
2471                     {
2472                         *bp++ = ' ';
2473                         buf_len++;
2474                     }
2475                 }
2476                 continue;
2477             }
2478             else if (cmatch)
2479             {
2480                 /*
2481                     match failed, so we have to copy what matched before
2482                     falling through and copying this character.  In reality,
2483                     this will only ever be just the newline character, but
2484                     it doesn't hurt to be precise.
2485                 */
2486                 strncpy(bp, cont_seq, cmatch);
2487                 bp += cmatch;
2488                 buf_len += cmatch;
2489                 cmatch = 0;
2490             }
2491         }
2492
2493         // copy this char
2494         *bp++ = data[i];
2495         buf_len++;
2496     }
2497
2498         buf[buf_len] = NULLCHAR;
2499 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2500         next_out = 0;
2501         leftover_start = 0;
2502
2503         i = 0;
2504         while (i < buf_len) {
2505             /* Deal with part of the TELNET option negotiation
2506                protocol.  We refuse to do anything beyond the
2507                defaults, except that we allow the WILL ECHO option,
2508                which ICS uses to turn off password echoing when we are
2509                directly connected to it.  We reject this option
2510                if localLineEditing mode is on (always on in xboard)
2511                and we are talking to port 23, which might be a real
2512                telnet server that will try to keep WILL ECHO on permanently.
2513              */
2514             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2515                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2516                 unsigned char option;
2517                 oldi = i;
2518                 switch ((unsigned char) buf[++i]) {
2519                   case TN_WILL:
2520                     if (appData.debugMode)
2521                       fprintf(debugFP, "\n<WILL ");
2522                     switch (option = (unsigned char) buf[++i]) {
2523                       case TN_ECHO:
2524                         if (appData.debugMode)
2525                           fprintf(debugFP, "ECHO ");
2526                         /* Reply only if this is a change, according
2527                            to the protocol rules. */
2528                         if (remoteEchoOption) break;
2529                         if (appData.localLineEditing &&
2530                             atoi(appData.icsPort) == TN_PORT) {
2531                             TelnetRequest(TN_DONT, TN_ECHO);
2532                         } else {
2533                             EchoOff();
2534                             TelnetRequest(TN_DO, TN_ECHO);
2535                             remoteEchoOption = TRUE;
2536                         }
2537                         break;
2538                       default:
2539                         if (appData.debugMode)
2540                           fprintf(debugFP, "%d ", option);
2541                         /* Whatever this is, we don't want it. */
2542                         TelnetRequest(TN_DONT, option);
2543                         break;
2544                     }
2545                     break;
2546                   case TN_WONT:
2547                     if (appData.debugMode)
2548                       fprintf(debugFP, "\n<WONT ");
2549                     switch (option = (unsigned char) buf[++i]) {
2550                       case TN_ECHO:
2551                         if (appData.debugMode)
2552                           fprintf(debugFP, "ECHO ");
2553                         /* Reply only if this is a change, according
2554                            to the protocol rules. */
2555                         if (!remoteEchoOption) break;
2556                         EchoOn();
2557                         TelnetRequest(TN_DONT, TN_ECHO);
2558                         remoteEchoOption = FALSE;
2559                         break;
2560                       default:
2561                         if (appData.debugMode)
2562                           fprintf(debugFP, "%d ", (unsigned char) option);
2563                         /* Whatever this is, it must already be turned
2564                            off, because we never agree to turn on
2565                            anything non-default, so according to the
2566                            protocol rules, we don't reply. */
2567                         break;
2568                     }
2569                     break;
2570                   case TN_DO:
2571                     if (appData.debugMode)
2572                       fprintf(debugFP, "\n<DO ");
2573                     switch (option = (unsigned char) buf[++i]) {
2574                       default:
2575                         /* Whatever this is, we refuse to do it. */
2576                         if (appData.debugMode)
2577                           fprintf(debugFP, "%d ", option);
2578                         TelnetRequest(TN_WONT, option);
2579                         break;
2580                     }
2581                     break;
2582                   case TN_DONT:
2583                     if (appData.debugMode)
2584                       fprintf(debugFP, "\n<DONT ");
2585                     switch (option = (unsigned char) buf[++i]) {
2586                       default:
2587                         if (appData.debugMode)
2588                           fprintf(debugFP, "%d ", option);
2589                         /* Whatever this is, we are already not doing
2590                            it, because we never agree to do anything
2591                            non-default, so according to the protocol
2592                            rules, we don't reply. */
2593                         break;
2594                     }
2595                     break;
2596                   case TN_IAC:
2597                     if (appData.debugMode)
2598                       fprintf(debugFP, "\n<IAC ");
2599                     /* Doubled IAC; pass it through */
2600                     i--;
2601                     break;
2602                   default:
2603                     if (appData.debugMode)
2604                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2605                     /* Drop all other telnet commands on the floor */
2606                     break;
2607                 }
2608                 if (oldi > next_out)
2609                   SendToPlayer(&buf[next_out], oldi - next_out);
2610                 if (++i > next_out)
2611                   next_out = i;
2612                 continue;
2613             }
2614
2615             /* OK, this at least will *usually* work */
2616             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2617                 loggedOn = TRUE;
2618             }
2619
2620             if (loggedOn && !intfSet) {
2621                 if (ics_type == ICS_ICC) {
2622                   snprintf(str, MSG_SIZ,
2623                           "/set-quietly interface %s\n/set-quietly style 12\n",
2624                           programVersion);
2625                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2626                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2627                 } else if (ics_type == ICS_CHESSNET) {
2628                   snprintf(str, MSG_SIZ, "/style 12\n");
2629                 } else {
2630                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2631                   strcat(str, programVersion);
2632                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2633                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2634                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2635 #ifdef WIN32
2636                   strcat(str, "$iset nohighlight 1\n");
2637 #endif
2638                   strcat(str, "$iset lock 1\n$style 12\n");
2639                 }
2640                 SendToICS(str);
2641                 NotifyFrontendLogin();
2642                 intfSet = TRUE;
2643             }
2644
2645             if (started == STARTED_COMMENT) {
2646                 /* Accumulate characters in comment */
2647                 parse[parse_pos++] = buf[i];
2648                 if (buf[i] == '\n') {
2649                     parse[parse_pos] = NULLCHAR;
2650                     if(chattingPartner>=0) {
2651                         char mess[MSG_SIZ];
2652                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2653                         OutputChatMessage(chattingPartner, mess);
2654                         chattingPartner = -1;
2655                         next_out = i+1; // [HGM] suppress printing in ICS window
2656                     } else
2657                     if(!suppressKibitz) // [HGM] kibitz
2658                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2659                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2660                         int nrDigit = 0, nrAlph = 0, j;
2661                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2662                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2663                         parse[parse_pos] = NULLCHAR;
2664                         // try to be smart: if it does not look like search info, it should go to
2665                         // ICS interaction window after all, not to engine-output window.
2666                         for(j=0; j<parse_pos; j++) { // count letters and digits
2667                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2668                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2669                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2670                         }
2671                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2672                             int depth=0; float score;
2673                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2674                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2675                                 pvInfoList[forwardMostMove-1].depth = depth;
2676                                 pvInfoList[forwardMostMove-1].score = 100*score;
2677                             }
2678                             OutputKibitz(suppressKibitz, parse);
2679                         } else {
2680                             char tmp[MSG_SIZ];
2681                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2682                             SendToPlayer(tmp, strlen(tmp));
2683                         }
2684                         next_out = i+1; // [HGM] suppress printing in ICS window
2685                     }
2686                     started = STARTED_NONE;
2687                 } else {
2688                     /* Don't match patterns against characters in comment */
2689                     i++;
2690                     continue;
2691                 }
2692             }
2693             if (started == STARTED_CHATTER) {
2694                 if (buf[i] != '\n') {
2695                     /* Don't match patterns against characters in chatter */
2696                     i++;
2697                     continue;
2698                 }
2699                 started = STARTED_NONE;
2700                 if(suppressKibitz) next_out = i+1;
2701             }
2702
2703             /* Kludge to deal with rcmd protocol */
2704             if (firstTime && looking_at(buf, &i, "\001*")) {
2705                 DisplayFatalError(&buf[1], 0, 1);
2706                 continue;
2707             } else {
2708                 firstTime = FALSE;
2709             }
2710
2711             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2712                 ics_type = ICS_ICC;
2713                 ics_prefix = "/";
2714                 if (appData.debugMode)
2715                   fprintf(debugFP, "ics_type %d\n", ics_type);
2716                 continue;
2717             }
2718             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2719                 ics_type = ICS_FICS;
2720                 ics_prefix = "$";
2721                 if (appData.debugMode)
2722                   fprintf(debugFP, "ics_type %d\n", ics_type);
2723                 continue;
2724             }
2725             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2726                 ics_type = ICS_CHESSNET;
2727                 ics_prefix = "/";
2728                 if (appData.debugMode)
2729                   fprintf(debugFP, "ics_type %d\n", ics_type);
2730                 continue;
2731             }
2732
2733             if (!loggedOn &&
2734                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2735                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2736                  looking_at(buf, &i, "will be \"*\""))) {
2737               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2738               continue;
2739             }
2740
2741             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2742               char buf[MSG_SIZ];
2743               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2744               DisplayIcsInteractionTitle(buf);
2745               have_set_title = TRUE;
2746             }
2747
2748             /* skip finger notes */
2749             if (started == STARTED_NONE &&
2750                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2751                  (buf[i] == '1' && buf[i+1] == '0')) &&
2752                 buf[i+2] == ':' && buf[i+3] == ' ') {
2753               started = STARTED_CHATTER;
2754               i += 3;
2755               continue;
2756             }
2757
2758             oldi = i;
2759             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2760             if(appData.seekGraph) {
2761                 if(soughtPending && MatchSoughtLine(buf+i)) {
2762                     i = strstr(buf+i, "rated") - buf;
2763                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2764                     next_out = leftover_start = i;
2765                     started = STARTED_CHATTER;
2766                     suppressKibitz = TRUE;
2767                     continue;
2768                 }
2769                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2770                         && looking_at(buf, &i, "* ads displayed")) {
2771                     soughtPending = FALSE;
2772                     seekGraphUp = TRUE;
2773                     DrawSeekGraph();
2774                     continue;
2775                 }
2776                 if(appData.autoRefresh) {
2777                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2778                         int s = (ics_type == ICS_ICC); // ICC format differs
2779                         if(seekGraphUp)
2780                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2781                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2782                         looking_at(buf, &i, "*% "); // eat prompt
2783                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2784                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2785                         next_out = i; // suppress
2786                         continue;
2787                     }
2788                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2789                         char *p = star_match[0];
2790                         while(*p) {
2791                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2792                             while(*p && *p++ != ' '); // next
2793                         }
2794                         looking_at(buf, &i, "*% "); // eat prompt
2795                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2796                         next_out = i;
2797                         continue;
2798                     }
2799                 }
2800             }
2801
2802             /* skip formula vars */
2803             if (started == STARTED_NONE &&
2804                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2805               started = STARTED_CHATTER;
2806               i += 3;
2807               continue;
2808             }
2809
2810             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2811             if (appData.autoKibitz && started == STARTED_NONE &&
2812                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2813                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2814                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2815                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2816                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2817                         suppressKibitz = TRUE;
2818                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2819                         next_out = i;
2820                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2821                                 && (gameMode == IcsPlayingWhite)) ||
2822                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2823                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2824                             started = STARTED_CHATTER; // own kibitz we simply discard
2825                         else {
2826                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2827                             parse_pos = 0; parse[0] = NULLCHAR;
2828                             savingComment = TRUE;
2829                             suppressKibitz = gameMode != IcsObserving ? 2 :
2830                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2831                         }
2832                         continue;
2833                 } else
2834                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2835                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2836                          && atoi(star_match[0])) {
2837                     // suppress the acknowledgements of our own autoKibitz
2838                     char *p;
2839                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2840                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2841                     SendToPlayer(star_match[0], strlen(star_match[0]));
2842                     if(looking_at(buf, &i, "*% ")) // eat prompt
2843                         suppressKibitz = FALSE;
2844                     next_out = i;
2845                     continue;
2846                 }
2847             } // [HGM] kibitz: end of patch
2848
2849             // [HGM] chat: intercept tells by users for which we have an open chat window
2850             channel = -1;
2851             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2852                                            looking_at(buf, &i, "* whispers:") ||
2853                                            looking_at(buf, &i, "* kibitzes:") ||
2854                                            looking_at(buf, &i, "* shouts:") ||
2855                                            looking_at(buf, &i, "* c-shouts:") ||
2856                                            looking_at(buf, &i, "--> * ") ||
2857                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2858                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2859                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2860                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2861                 int p;
2862                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2863                 chattingPartner = -1;
2864
2865                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2866                 for(p=0; p<MAX_CHAT; p++) {
2867                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2868                     talker[0] = '['; strcat(talker, "] ");
2869                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2870                     chattingPartner = p; break;
2871                     }
2872                 } else
2873                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2874                 for(p=0; p<MAX_CHAT; p++) {
2875                     if(!strcmp("kibitzes", chatPartner[p])) {
2876                         talker[0] = '['; strcat(talker, "] ");
2877                         chattingPartner = p; break;
2878                     }
2879                 } else
2880                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2881                 for(p=0; p<MAX_CHAT; p++) {
2882                     if(!strcmp("whispers", chatPartner[p])) {
2883                         talker[0] = '['; strcat(talker, "] ");
2884                         chattingPartner = p; break;
2885                     }
2886                 } else
2887                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2888                   if(buf[i-8] == '-' && buf[i-3] == 't')
2889                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2890                     if(!strcmp("c-shouts", chatPartner[p])) {
2891                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2892                         chattingPartner = p; break;
2893                     }
2894                   }
2895                   if(chattingPartner < 0)
2896                   for(p=0; p<MAX_CHAT; p++) {
2897                     if(!strcmp("shouts", chatPartner[p])) {
2898                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2899                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2900                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2901                         chattingPartner = p; break;
2902                     }
2903                   }
2904                 }
2905                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2906                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2907                     talker[0] = 0; Colorize(ColorTell, FALSE);
2908                     chattingPartner = p; break;
2909                 }
2910                 if(chattingPartner<0) i = oldi; else {
2911                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2912                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2913                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2914                     started = STARTED_COMMENT;
2915                     parse_pos = 0; parse[0] = NULLCHAR;
2916                     savingComment = 3 + chattingPartner; // counts as TRUE
2917                     suppressKibitz = TRUE;
2918                     continue;
2919                 }
2920             } // [HGM] chat: end of patch
2921
2922             if (appData.zippyTalk || appData.zippyPlay) {
2923                 /* [DM] Backup address for color zippy lines */
2924                 backup = i;
2925 #if ZIPPY
2926                if (loggedOn == TRUE)
2927                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2928                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2929 #endif
2930             } // [DM] 'else { ' deleted
2931                 if (
2932                     /* Regular tells and says */
2933                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2934                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2935                     looking_at(buf, &i, "* says: ") ||
2936                     /* Don't color "message" or "messages" output */
2937                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2938                     looking_at(buf, &i, "*. * at *:*: ") ||
2939                     looking_at(buf, &i, "--* (*:*): ") ||
2940                     /* Message notifications (same color as tells) */
2941                     looking_at(buf, &i, "* has left a message ") ||
2942                     looking_at(buf, &i, "* just sent you a message:\n") ||
2943                     /* Whispers and kibitzes */
2944                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2945                     looking_at(buf, &i, "* kibitzes: ") ||
2946                     /* Channel tells */
2947                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2948
2949                   if (tkind == 1 && strchr(star_match[0], ':')) {
2950                       /* Avoid "tells you:" spoofs in channels */
2951                      tkind = 3;
2952                   }
2953                   if (star_match[0][0] == NULLCHAR ||
2954                       strchr(star_match[0], ' ') ||
2955                       (tkind == 3 && strchr(star_match[1], ' '))) {
2956                     /* Reject bogus matches */
2957                     i = oldi;
2958                   } else {
2959                     if (appData.colorize) {
2960                       if (oldi > next_out) {
2961                         SendToPlayer(&buf[next_out], oldi - next_out);
2962                         next_out = oldi;
2963                       }
2964                       switch (tkind) {
2965                       case 1:
2966                         Colorize(ColorTell, FALSE);
2967                         curColor = ColorTell;
2968                         break;
2969                       case 2:
2970                         Colorize(ColorKibitz, FALSE);
2971                         curColor = ColorKibitz;
2972                         break;
2973                       case 3:
2974                         p = strrchr(star_match[1], '(');
2975                         if (p == NULL) {
2976                           p = star_match[1];
2977                         } else {
2978                           p++;
2979                         }
2980                         if (atoi(p) == 1) {
2981                           Colorize(ColorChannel1, FALSE);
2982                           curColor = ColorChannel1;
2983                         } else {
2984                           Colorize(ColorChannel, FALSE);
2985                           curColor = ColorChannel;
2986                         }
2987                         break;
2988                       case 5:
2989                         curColor = ColorNormal;
2990                         break;
2991                       }
2992                     }
2993                     if (started == STARTED_NONE && appData.autoComment &&
2994                         (gameMode == IcsObserving ||
2995                          gameMode == IcsPlayingWhite ||
2996                          gameMode == IcsPlayingBlack)) {
2997                       parse_pos = i - oldi;
2998                       memcpy(parse, &buf[oldi], parse_pos);
2999                       parse[parse_pos] = NULLCHAR;
3000                       started = STARTED_COMMENT;
3001                       savingComment = TRUE;
3002                     } else {
3003                       started = STARTED_CHATTER;
3004                       savingComment = FALSE;
3005                     }
3006                     loggedOn = TRUE;
3007                     continue;
3008                   }
3009                 }
3010
3011                 if (looking_at(buf, &i, "* s-shouts: ") ||
3012                     looking_at(buf, &i, "* c-shouts: ")) {
3013                     if (appData.colorize) {
3014                         if (oldi > next_out) {
3015                             SendToPlayer(&buf[next_out], oldi - next_out);
3016                             next_out = oldi;
3017                         }
3018                         Colorize(ColorSShout, FALSE);
3019                         curColor = ColorSShout;
3020                     }
3021                     loggedOn = TRUE;
3022                     started = STARTED_CHATTER;
3023                     continue;
3024                 }
3025
3026                 if (looking_at(buf, &i, "--->")) {
3027                     loggedOn = TRUE;
3028                     continue;
3029                 }
3030
3031                 if (looking_at(buf, &i, "* shouts: ") ||
3032                     looking_at(buf, &i, "--> ")) {
3033                     if (appData.colorize) {
3034                         if (oldi > next_out) {
3035                             SendToPlayer(&buf[next_out], oldi - next_out);
3036                             next_out = oldi;
3037                         }
3038                         Colorize(ColorShout, FALSE);
3039                         curColor = ColorShout;
3040                     }
3041                     loggedOn = TRUE;
3042                     started = STARTED_CHATTER;
3043                     continue;
3044                 }
3045
3046                 if (looking_at( buf, &i, "Challenge:")) {
3047                     if (appData.colorize) {
3048                         if (oldi > next_out) {
3049                             SendToPlayer(&buf[next_out], oldi - next_out);
3050                             next_out = oldi;
3051                         }
3052                         Colorize(ColorChallenge, FALSE);
3053                         curColor = ColorChallenge;
3054                     }
3055                     loggedOn = TRUE;
3056                     continue;
3057                 }
3058
3059                 if (looking_at(buf, &i, "* offers you") ||
3060                     looking_at(buf, &i, "* offers to be") ||
3061                     looking_at(buf, &i, "* would like to") ||
3062                     looking_at(buf, &i, "* requests to") ||
3063                     looking_at(buf, &i, "Your opponent offers") ||
3064                     looking_at(buf, &i, "Your opponent requests")) {
3065
3066                     if (appData.colorize) {
3067                         if (oldi > next_out) {
3068                             SendToPlayer(&buf[next_out], oldi - next_out);
3069                             next_out = oldi;
3070                         }
3071                         Colorize(ColorRequest, FALSE);
3072                         curColor = ColorRequest;
3073                     }
3074                     continue;
3075                 }
3076
3077                 if (looking_at(buf, &i, "* (*) seeking")) {
3078                     if (appData.colorize) {
3079                         if (oldi > next_out) {
3080                             SendToPlayer(&buf[next_out], oldi - next_out);
3081                             next_out = oldi;
3082                         }
3083                         Colorize(ColorSeek, FALSE);
3084                         curColor = ColorSeek;
3085                     }
3086                     continue;
3087             }
3088
3089             if (looking_at(buf, &i, "\\   ")) {
3090                 if (prevColor != ColorNormal) {
3091                     if (oldi > next_out) {
3092                         SendToPlayer(&buf[next_out], oldi - next_out);
3093                         next_out = oldi;
3094                     }
3095                     Colorize(prevColor, TRUE);
3096                     curColor = prevColor;
3097                 }
3098                 if (savingComment) {
3099                     parse_pos = i - oldi;
3100                     memcpy(parse, &buf[oldi], parse_pos);
3101                     parse[parse_pos] = NULLCHAR;
3102                     started = STARTED_COMMENT;
3103                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3104                         chattingPartner = savingComment - 3; // kludge to remember the box
3105                 } else {
3106                     started = STARTED_CHATTER;
3107                 }
3108                 continue;
3109             }
3110
3111             if (looking_at(buf, &i, "Black Strength :") ||
3112                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3113                 looking_at(buf, &i, "<10>") ||
3114                 looking_at(buf, &i, "#@#")) {
3115                 /* Wrong board style */
3116                 loggedOn = TRUE;
3117                 SendToICS(ics_prefix);
3118                 SendToICS("set style 12\n");
3119                 SendToICS(ics_prefix);
3120                 SendToICS("refresh\n");
3121                 continue;
3122             }
3123
3124             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3125                 ICSInitScript();
3126                 have_sent_ICS_logon = 1;
3127                 continue;
3128             }
3129
3130             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3131                 (looking_at(buf, &i, "\n<12> ") ||
3132                  looking_at(buf, &i, "<12> "))) {
3133                 loggedOn = TRUE;
3134                 if (oldi > next_out) {
3135                     SendToPlayer(&buf[next_out], oldi - next_out);
3136                 }
3137                 next_out = i;
3138                 started = STARTED_BOARD;
3139                 parse_pos = 0;
3140                 continue;
3141             }
3142
3143             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3144                 looking_at(buf, &i, "<b1> ")) {
3145                 if (oldi > next_out) {
3146                     SendToPlayer(&buf[next_out], oldi - next_out);
3147                 }
3148                 next_out = i;
3149                 started = STARTED_HOLDINGS;
3150                 parse_pos = 0;
3151                 continue;
3152             }
3153
3154             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3155                 loggedOn = TRUE;
3156                 /* Header for a move list -- first line */
3157
3158                 switch (ics_getting_history) {
3159                   case H_FALSE:
3160                     switch (gameMode) {
3161                       case IcsIdle:
3162                       case BeginningOfGame:
3163                         /* User typed "moves" or "oldmoves" while we
3164                            were idle.  Pretend we asked for these
3165                            moves and soak them up so user can step
3166                            through them and/or save them.
3167                            */
3168                         Reset(FALSE, TRUE);
3169                         gameMode = IcsObserving;
3170                         ModeHighlight();
3171                         ics_gamenum = -1;
3172                         ics_getting_history = H_GOT_UNREQ_HEADER;
3173                         break;
3174                       case EditGame: /*?*/
3175                       case EditPosition: /*?*/
3176                         /* Should above feature work in these modes too? */
3177                         /* For now it doesn't */
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                       default:
3181                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3182                         break;
3183                     }
3184                     break;
3185                   case H_REQUESTED:
3186                     /* Is this the right one? */
3187                     if (gameInfo.white && gameInfo.black &&
3188                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3189                         strcmp(gameInfo.black, star_match[2]) == 0) {
3190                         /* All is well */
3191                         ics_getting_history = H_GOT_REQ_HEADER;
3192                     }
3193                     break;
3194                   case H_GOT_REQ_HEADER:
3195                   case H_GOT_UNREQ_HEADER:
3196                   case H_GOT_UNWANTED_HEADER:
3197                   case H_GETTING_MOVES:
3198                     /* Should not happen */
3199                     DisplayError(_("Error gathering move list: two headers"), 0);
3200                     ics_getting_history = H_FALSE;
3201                     break;
3202                 }
3203
3204                 /* Save player ratings into gameInfo if needed */
3205                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3206                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3207                     (gameInfo.whiteRating == -1 ||
3208                      gameInfo.blackRating == -1)) {
3209
3210                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3211                     gameInfo.blackRating = string_to_rating(star_match[3]);
3212                     if (appData.debugMode)
3213                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3214                               gameInfo.whiteRating, gameInfo.blackRating);
3215                 }
3216                 continue;
3217             }
3218
3219             if (looking_at(buf, &i,
3220               "* * match, initial time: * minute*, increment: * second")) {
3221                 /* Header for a move list -- second line */
3222                 /* Initial board will follow if this is a wild game */
3223                 if (gameInfo.event != NULL) free(gameInfo.event);
3224                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3225                 gameInfo.event = StrSave(str);
3226                 /* [HGM] we switched variant. Translate boards if needed. */
3227                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3228                 continue;
3229             }
3230
3231             if (looking_at(buf, &i, "Move  ")) {
3232                 /* Beginning of a move list */
3233                 switch (ics_getting_history) {
3234                   case H_FALSE:
3235                     /* Normally should not happen */
3236                     /* Maybe user hit reset while we were parsing */
3237                     break;
3238                   case H_REQUESTED:
3239                     /* Happens if we are ignoring a move list that is not
3240                      * the one we just requested.  Common if the user
3241                      * tries to observe two games without turning off
3242                      * getMoveList */
3243                     break;
3244                   case H_GETTING_MOVES:
3245                     /* Should not happen */
3246                     DisplayError(_("Error gathering move list: nested"), 0);
3247                     ics_getting_history = H_FALSE;
3248                     break;
3249                   case H_GOT_REQ_HEADER:
3250                     ics_getting_history = H_GETTING_MOVES;
3251                     started = STARTED_MOVES;
3252                     parse_pos = 0;
3253                     if (oldi > next_out) {
3254                         SendToPlayer(&buf[next_out], oldi - next_out);
3255                     }
3256                     break;
3257                   case H_GOT_UNREQ_HEADER:
3258                     ics_getting_history = H_GETTING_MOVES;
3259                     started = STARTED_MOVES_NOHIDE;
3260                     parse_pos = 0;
3261                     break;
3262                   case H_GOT_UNWANTED_HEADER:
3263                     ics_getting_history = H_FALSE;
3264                     break;
3265                 }
3266                 continue;
3267             }
3268
3269             if (looking_at(buf, &i, "% ") ||
3270                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3271                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3272                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3273                     soughtPending = FALSE;
3274                     seekGraphUp = TRUE;
3275                     DrawSeekGraph();
3276                 }
3277                 if(suppressKibitz) next_out = i;
3278                 savingComment = FALSE;
3279                 suppressKibitz = 0;
3280                 switch (started) {
3281                   case STARTED_MOVES:
3282                   case STARTED_MOVES_NOHIDE:
3283                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3284                     parse[parse_pos + i - oldi] = NULLCHAR;
3285                     ParseGameHistory(parse);
3286 #if ZIPPY
3287                     if (appData.zippyPlay && first.initDone) {
3288                         FeedMovesToProgram(&first, forwardMostMove);
3289                         if (gameMode == IcsPlayingWhite) {
3290                             if (WhiteOnMove(forwardMostMove)) {
3291                                 if (first.sendTime) {
3292                                   if (first.useColors) {
3293                                     SendToProgram("black\n", &first);
3294                                   }
3295                                   SendTimeRemaining(&first, TRUE);
3296                                 }
3297                                 if (first.useColors) {
3298                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3299                                 }
3300                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3301                                 first.maybeThinking = TRUE;
3302                             } else {
3303                                 if (first.usePlayother) {
3304                                   if (first.sendTime) {
3305                                     SendTimeRemaining(&first, TRUE);
3306                                   }
3307                                   SendToProgram("playother\n", &first);
3308                                   firstMove = FALSE;
3309                                 } else {
3310                                   firstMove = TRUE;
3311                                 }
3312                             }
3313                         } else if (gameMode == IcsPlayingBlack) {
3314                             if (!WhiteOnMove(forwardMostMove)) {
3315                                 if (first.sendTime) {
3316                                   if (first.useColors) {
3317                                     SendToProgram("white\n", &first);
3318                                   }
3319                                   SendTimeRemaining(&first, FALSE);
3320                                 }
3321                                 if (first.useColors) {
3322                                   SendToProgram("black\n", &first);
3323                                 }
3324                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3325                                 first.maybeThinking = TRUE;
3326                             } else {
3327                                 if (first.usePlayother) {
3328                                   if (first.sendTime) {
3329                                     SendTimeRemaining(&first, FALSE);
3330                                   }
3331                                   SendToProgram("playother\n", &first);
3332                                   firstMove = FALSE;
3333                                 } else {
3334                                   firstMove = TRUE;
3335                                 }
3336                             }
3337                         }
3338                     }
3339 #endif
3340                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3341                         /* Moves came from oldmoves or moves command
3342                            while we weren't doing anything else.
3343                            */
3344                         currentMove = forwardMostMove;
3345                         ClearHighlights();/*!!could figure this out*/
3346                         flipView = appData.flipView;
3347                         DrawPosition(TRUE, boards[currentMove]);
3348                         DisplayBothClocks();
3349                         snprintf(str, MSG_SIZ, "%s vs. %s",
3350                                 gameInfo.white, gameInfo.black);
3351                         DisplayTitle(str);
3352                         gameMode = IcsIdle;
3353                     } else {
3354                         /* Moves were history of an active game */
3355                         if (gameInfo.resultDetails != NULL) {
3356                             free(gameInfo.resultDetails);
3357                             gameInfo.resultDetails = NULL;
3358                         }
3359                     }
3360                     HistorySet(parseList, backwardMostMove,
3361                                forwardMostMove, currentMove-1);
3362                     DisplayMove(currentMove - 1);
3363                     if (started == STARTED_MOVES) next_out = i;
3364                     started = STARTED_NONE;
3365                     ics_getting_history = H_FALSE;
3366                     break;
3367
3368                   case STARTED_OBSERVE:
3369                     started = STARTED_NONE;
3370                     SendToICS(ics_prefix);
3371                     SendToICS("refresh\n");
3372                     break;
3373
3374                   default:
3375                     break;
3376                 }
3377                 if(bookHit) { // [HGM] book: simulate book reply
3378                     static char bookMove[MSG_SIZ]; // a bit generous?
3379
3380                     programStats.nodes = programStats.depth = programStats.time =
3381                     programStats.score = programStats.got_only_move = 0;
3382                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3383
3384                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3385                     strcat(bookMove, bookHit);
3386                     HandleMachineMove(bookMove, &first);
3387                 }
3388                 continue;
3389             }
3390
3391             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3392                  started == STARTED_HOLDINGS ||
3393                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3394                 /* Accumulate characters in move list or board */
3395                 parse[parse_pos++] = buf[i];
3396             }
3397
3398             /* Start of game messages.  Mostly we detect start of game
3399                when the first board image arrives.  On some versions
3400                of the ICS, though, we need to do a "refresh" after starting
3401                to observe in order to get the current board right away. */
3402             if (looking_at(buf, &i, "Adding game * to observation list")) {
3403                 started = STARTED_OBSERVE;
3404                 continue;
3405             }
3406
3407             /* Handle auto-observe */
3408             if (appData.autoObserve &&
3409                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3410                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3411                 char *player;
3412                 /* Choose the player that was highlighted, if any. */
3413                 if (star_match[0][0] == '\033' ||
3414                     star_match[1][0] != '\033') {
3415                     player = star_match[0];
3416                 } else {
3417                     player = star_match[2];
3418                 }
3419                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3420                         ics_prefix, StripHighlightAndTitle(player));
3421                 SendToICS(str);
3422
3423                 /* Save ratings from notify string */
3424                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3425                 player1Rating = string_to_rating(star_match[1]);
3426                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3427                 player2Rating = string_to_rating(star_match[3]);
3428
3429                 if (appData.debugMode)
3430                   fprintf(debugFP,
3431                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3432                           player1Name, player1Rating,
3433                           player2Name, player2Rating);
3434
3435                 continue;
3436             }
3437
3438             /* Deal with automatic examine mode after a game,
3439                and with IcsObserving -> IcsExamining transition */
3440             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3441                 looking_at(buf, &i, "has made you an examiner of game *")) {
3442
3443                 int gamenum = atoi(star_match[0]);
3444                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3445                     gamenum == ics_gamenum) {
3446                     /* We were already playing or observing this game;
3447                        no need to refetch history */
3448                     gameMode = IcsExamining;
3449                     if (pausing) {
3450                         pauseExamForwardMostMove = forwardMostMove;
3451                     } else if (currentMove < forwardMostMove) {
3452                         ForwardInner(forwardMostMove);
3453                     }
3454                 } else {
3455                     /* I don't think this case really can happen */
3456                     SendToICS(ics_prefix);
3457                     SendToICS("refresh\n");
3458                 }
3459                 continue;
3460             }
3461
3462             /* Error messages */
3463 //          if (ics_user_moved) {
3464             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3465                 if (looking_at(buf, &i, "Illegal move") ||
3466                     looking_at(buf, &i, "Not a legal move") ||
3467                     looking_at(buf, &i, "Your king is in check") ||
3468                     looking_at(buf, &i, "It isn't your turn") ||
3469                     looking_at(buf, &i, "It is not your move")) {
3470                     /* Illegal move */
3471                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3472                         currentMove = forwardMostMove-1;
3473                         DisplayMove(currentMove - 1); /* before DMError */
3474                         DrawPosition(FALSE, boards[currentMove]);
3475                         SwitchClocks(forwardMostMove-1); // [HGM] race
3476                         DisplayBothClocks();
3477                     }
3478                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3479                     ics_user_moved = 0;
3480                     continue;
3481                 }
3482             }
3483
3484             if (looking_at(buf, &i, "still have time") ||
3485                 looking_at(buf, &i, "not out of time") ||
3486                 looking_at(buf, &i, "either player is out of time") ||
3487                 looking_at(buf, &i, "has timeseal; checking")) {
3488                 /* We must have called his flag a little too soon */
3489                 whiteFlag = blackFlag = FALSE;
3490                 continue;
3491             }
3492
3493             if (looking_at(buf, &i, "added * seconds to") ||
3494                 looking_at(buf, &i, "seconds were added to")) {
3495                 /* Update the clocks */
3496                 SendToICS(ics_prefix);
3497                 SendToICS("refresh\n");
3498                 continue;
3499             }
3500
3501             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3502                 ics_clock_paused = TRUE;
3503                 StopClocks();
3504                 continue;
3505             }
3506
3507             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3508                 ics_clock_paused = FALSE;
3509                 StartClocks();
3510                 continue;
3511             }
3512
3513             /* Grab player ratings from the Creating: message.
3514                Note we have to check for the special case when
3515                the ICS inserts things like [white] or [black]. */
3516             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3517                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3518                 /* star_matches:
3519                    0    player 1 name (not necessarily white)
3520                    1    player 1 rating
3521                    2    empty, white, or black (IGNORED)
3522                    3    player 2 name (not necessarily black)
3523                    4    player 2 rating
3524
3525                    The names/ratings are sorted out when the game
3526                    actually starts (below).
3527                 */
3528                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3529                 player1Rating = string_to_rating(star_match[1]);
3530                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3531                 player2Rating = string_to_rating(star_match[4]);
3532
3533                 if (appData.debugMode)
3534                   fprintf(debugFP,
3535                           "Ratings from 'Creating:' %s %d, %s %d\n",
3536                           player1Name, player1Rating,
3537                           player2Name, player2Rating);
3538
3539                 continue;
3540             }
3541
3542             /* Improved generic start/end-of-game messages */
3543             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3544                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3545                 /* If tkind == 0: */
3546                 /* star_match[0] is the game number */
3547                 /*           [1] is the white player's name */
3548                 /*           [2] is the black player's name */
3549                 /* For end-of-game: */
3550                 /*           [3] is the reason for the game end */
3551                 /*           [4] is a PGN end game-token, preceded by " " */
3552                 /* For start-of-game: */
3553                 /*           [3] begins with "Creating" or "Continuing" */
3554                 /*           [4] is " *" or empty (don't care). */
3555                 int gamenum = atoi(star_match[0]);
3556                 char *whitename, *blackname, *why, *endtoken;
3557                 ChessMove endtype = EndOfFile;
3558
3559                 if (tkind == 0) {
3560                   whitename = star_match[1];
3561                   blackname = star_match[2];
3562                   why = star_match[3];
3563                   endtoken = star_match[4];
3564                 } else {
3565                   whitename = star_match[1];
3566                   blackname = star_match[3];
3567                   why = star_match[5];
3568                   endtoken = star_match[6];
3569                 }
3570
3571                 /* Game start messages */
3572                 if (strncmp(why, "Creating ", 9) == 0 ||
3573                     strncmp(why, "Continuing ", 11) == 0) {
3574                     gs_gamenum = gamenum;
3575                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3576                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3577 #if ZIPPY
3578                     if (appData.zippyPlay) {
3579                         ZippyGameStart(whitename, blackname);
3580                     }
3581 #endif /*ZIPPY*/
3582                     partnerBoardValid = FALSE; // [HGM] bughouse
3583                     continue;
3584                 }
3585
3586                 /* Game end messages */
3587                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3588                     ics_gamenum != gamenum) {
3589                     continue;
3590                 }
3591                 while (endtoken[0] == ' ') endtoken++;
3592                 switch (endtoken[0]) {
3593                   case '*':
3594                   default:
3595                     endtype = GameUnfinished;
3596                     break;
3597                   case '0':
3598                     endtype = BlackWins;
3599                     break;
3600                   case '1':
3601                     if (endtoken[1] == '/')
3602                       endtype = GameIsDrawn;
3603                     else
3604                       endtype = WhiteWins;
3605                     break;
3606                 }
3607                 GameEnds(endtype, why, GE_ICS);
3608 #if ZIPPY
3609                 if (appData.zippyPlay && first.initDone) {
3610                     ZippyGameEnd(endtype, why);
3611                     if (first.pr == NULL) {
3612                       /* Start the next process early so that we'll
3613                          be ready for the next challenge */
3614                       StartChessProgram(&first);
3615                     }
3616                     /* Send "new" early, in case this command takes
3617                        a long time to finish, so that we'll be ready
3618                        for the next challenge. */
3619                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3620                     Reset(TRUE, TRUE);
3621                 }
3622 #endif /*ZIPPY*/
3623                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3624                 continue;
3625             }
3626
3627             if (looking_at(buf, &i, "Removing game * from observation") ||
3628                 looking_at(buf, &i, "no longer observing game *") ||
3629                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3630                 if (gameMode == IcsObserving &&
3631                     atoi(star_match[0]) == ics_gamenum)
3632                   {
3633                       /* icsEngineAnalyze */
3634                       if (appData.icsEngineAnalyze) {
3635                             ExitAnalyzeMode();
3636                             ModeHighlight();
3637                       }
3638                       StopClocks();
3639                       gameMode = IcsIdle;
3640                       ics_gamenum = -1;
3641                       ics_user_moved = FALSE;
3642                   }
3643                 continue;
3644             }
3645
3646             if (looking_at(buf, &i, "no longer examining game *")) {
3647                 if (gameMode == IcsExamining &&
3648                     atoi(star_match[0]) == ics_gamenum)
3649                   {
3650                       gameMode = IcsIdle;
3651                       ics_gamenum = -1;
3652                       ics_user_moved = FALSE;
3653                   }
3654                 continue;
3655             }
3656
3657             /* Advance leftover_start past any newlines we find,
3658                so only partial lines can get reparsed */
3659             if (looking_at(buf, &i, "\n")) {
3660                 prevColor = curColor;
3661                 if (curColor != ColorNormal) {
3662                     if (oldi > next_out) {
3663                         SendToPlayer(&buf[next_out], oldi - next_out);
3664                         next_out = oldi;
3665                     }
3666                     Colorize(ColorNormal, FALSE);
3667                     curColor = ColorNormal;
3668                 }
3669                 if (started == STARTED_BOARD) {
3670                     started = STARTED_NONE;
3671                     parse[parse_pos] = NULLCHAR;
3672                     ParseBoard12(parse);
3673                     ics_user_moved = 0;
3674
3675                     /* Send premove here */
3676                     if (appData.premove) {
3677                       char str[MSG_SIZ];
3678                       if (currentMove == 0 &&
3679                           gameMode == IcsPlayingWhite &&
3680                           appData.premoveWhite) {
3681                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3682                         if (appData.debugMode)
3683                           fprintf(debugFP, "Sending premove:\n");
3684                         SendToICS(str);
3685                       } else if (currentMove == 1 &&
3686                                  gameMode == IcsPlayingBlack &&
3687                                  appData.premoveBlack) {
3688                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3689                         if (appData.debugMode)
3690                           fprintf(debugFP, "Sending premove:\n");
3691                         SendToICS(str);
3692                       } else if (gotPremove) {
3693                         gotPremove = 0;
3694                         ClearPremoveHighlights();
3695                         if (appData.debugMode)
3696                           fprintf(debugFP, "Sending premove:\n");
3697                           UserMoveEvent(premoveFromX, premoveFromY,
3698                                         premoveToX, premoveToY,
3699                                         premovePromoChar);
3700                       }
3701                     }
3702
3703                     /* Usually suppress following prompt */
3704                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3705                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3706                         if (looking_at(buf, &i, "*% ")) {
3707                             savingComment = FALSE;
3708                             suppressKibitz = 0;
3709                         }
3710                     }
3711                     next_out = i;
3712                 } else if (started == STARTED_HOLDINGS) {
3713                     int gamenum;
3714                     char new_piece[MSG_SIZ];
3715                     started = STARTED_NONE;
3716                     parse[parse_pos] = NULLCHAR;
3717                     if (appData.debugMode)
3718                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3719                                                         parse, currentMove);
3720                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3721                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3722                         if (gameInfo.variant == VariantNormal) {
3723                           /* [HGM] We seem to switch variant during a game!
3724                            * Presumably no holdings were displayed, so we have
3725                            * to move the position two files to the right to
3726                            * create room for them!
3727                            */
3728                           VariantClass newVariant;
3729                           switch(gameInfo.boardWidth) { // base guess on board width
3730                                 case 9:  newVariant = VariantShogi; break;
3731                                 case 10: newVariant = VariantGreat; break;
3732                                 default: newVariant = VariantCrazyhouse; break;
3733                           }
3734                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3735                           /* Get a move list just to see the header, which
3736                              will tell us whether this is really bug or zh */
3737                           if (ics_getting_history == H_FALSE) {
3738                             ics_getting_history = H_REQUESTED;
3739                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3740                             SendToICS(str);
3741                           }
3742                         }
3743                         new_piece[0] = NULLCHAR;
3744                         sscanf(parse, "game %d white [%s black [%s <- %s",
3745                                &gamenum, white_holding, black_holding,
3746                                new_piece);
3747                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3748                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3749                         /* [HGM] copy holdings to board holdings area */
3750                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3751                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3752                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3753 #if ZIPPY
3754                         if (appData.zippyPlay && first.initDone) {
3755                             ZippyHoldings(white_holding, black_holding,
3756                                           new_piece);
3757                         }
3758 #endif /*ZIPPY*/
3759                         if (tinyLayout || smallLayout) {
3760                             char wh[16], bh[16];
3761                             PackHolding(wh, white_holding);
3762                             PackHolding(bh, black_holding);
3763                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3764                                     gameInfo.white, gameInfo.black);
3765                         } else {
3766                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3767                                     gameInfo.white, white_holding,
3768                                     gameInfo.black, black_holding);
3769                         }
3770                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3771                         DrawPosition(FALSE, boards[currentMove]);
3772                         DisplayTitle(str);
3773                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3774                         sscanf(parse, "game %d white [%s black [%s <- %s",
3775                                &gamenum, white_holding, black_holding,
3776                                new_piece);
3777                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3778                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3779                         /* [HGM] copy holdings to partner-board holdings area */
3780                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3781                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3782                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3783                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3784                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3785                       }
3786                     }
3787                     /* Suppress following prompt */
3788                     if (looking_at(buf, &i, "*% ")) {
3789                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3790                         savingComment = FALSE;
3791                         suppressKibitz = 0;
3792                     }
3793                     next_out = i;