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