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