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