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