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