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