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