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