Add upload of game to ICS for examining
[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-squareSize/8-7)* log(tc)/log(95.) + 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-squareSize/8-7)* log((double)i)/log(95.) + 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             if(click == Press) {
2241                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2242                 lastDown = closest;
2243                 return TRUE;
2244             } // on press 'hit', only show info
2245             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246             sprintf(buf, "play %d\n", seekNrList[closest]);
2247             SendToICS(ics_prefix);
2248             SendToICS(buf);
2249             return TRUE; // let incoming board of started game pop down the graph
2250         } else if(click == Release) { // release 'miss' is ignored
2251             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252             if(moving == 2) { // right up-click
2253                 nrOfSeekAds = 0; // refresh graph
2254                 soughtPending = TRUE;
2255                 SendToICS(ics_prefix);
2256                 SendToICS("sought\n"); // should this be "sought all"?
2257             }
2258             return TRUE;
2259         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260         // press miss or release hit 'pop down' seek graph
2261         seekGraphUp = FALSE;
2262         DrawPosition(TRUE, NULL);
2263     }
2264     return TRUE;
2265 }
2266
2267 void
2268 read_from_ics(isr, closure, data, count, error)
2269      InputSourceRef isr;
2270      VOIDSTAR closure;
2271      char *data;
2272      int count;
2273      int error;
2274 {
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2284     
2285     static int started = STARTED_NONE;
2286     static char parse[20000];
2287     static int parse_pos = 0;
2288     static char buf[BUF_SIZE + 1];
2289     static int firstTime = TRUE, intfSet = FALSE;
2290     static ColorClass prevColor = ColorNormal;
2291     static int savingComment = FALSE;
2292     static int cmatch = 0; // continuation sequence match
2293     char *bp;
2294     char str[500];
2295     int i, oldi;
2296     int buf_len;
2297     int next_out;
2298     int tkind;
2299     int backup;    /* [DM] For zippy color lines */
2300     char *p;
2301     char talker[MSG_SIZ]; // [HGM] chat
2302     int channel;
2303
2304     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2305
2306     if (appData.debugMode) {
2307       if (!error) {
2308         fprintf(debugFP, "<ICS: ");
2309         show_bytes(debugFP, data, count);
2310         fprintf(debugFP, "\n");
2311       }
2312     }
2313
2314     if (appData.debugMode) { int f = forwardMostMove;
2315         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2318     }
2319     if (count > 0) {
2320         /* If last read ended with a partial line that we couldn't parse,
2321            prepend it to the new read and try again. */
2322         if (leftover_len > 0) {
2323             for (i=0; i<leftover_len; i++)
2324               buf[i] = buf[leftover_start + i];
2325         }
2326
2327     /* copy new characters into the buffer */
2328     bp = buf + leftover_len;
2329     buf_len=leftover_len;
2330     for (i=0; i<count; i++)
2331     {
2332         // ignore these
2333         if (data[i] == '\r')
2334             continue;
2335
2336         // join lines split by ICS?
2337         if (!appData.noJoin)
2338         {
2339             /*
2340                 Joining just consists of finding matches against the
2341                 continuation sequence, and discarding that sequence
2342                 if found instead of copying it.  So, until a match
2343                 fails, there's nothing to do since it might be the
2344                 complete sequence, and thus, something we don't want
2345                 copied.
2346             */
2347             if (data[i] == cont_seq[cmatch])
2348             {
2349                 cmatch++;
2350                 if (cmatch == strlen(cont_seq))
2351                 {
2352                     cmatch = 0; // complete match.  just reset the counter
2353
2354                     /*
2355                         it's possible for the ICS to not include the space
2356                         at the end of the last word, making our [correct]
2357                         join operation fuse two separate words.  the server
2358                         does this when the space occurs at the width setting.
2359                     */
2360                     if (!buf_len || buf[buf_len-1] != ' ')
2361                     {
2362                         *bp++ = ' ';
2363                         buf_len++;
2364                     }
2365                 }
2366                 continue;
2367             }
2368             else if (cmatch)
2369             {
2370                 /*
2371                     match failed, so we have to copy what matched before
2372                     falling through and copying this character.  In reality,
2373                     this will only ever be just the newline character, but
2374                     it doesn't hurt to be precise.
2375                 */
2376                 strncpy(bp, cont_seq, cmatch);
2377                 bp += cmatch;
2378                 buf_len += cmatch;
2379                 cmatch = 0;
2380             }
2381         }
2382
2383         // copy this char
2384         *bp++ = data[i];
2385         buf_len++;
2386     }
2387
2388         buf[buf_len] = NULLCHAR;
2389 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2390         next_out = 0;
2391         leftover_start = 0;
2392         
2393         i = 0;
2394         while (i < buf_len) {
2395             /* Deal with part of the TELNET option negotiation
2396                protocol.  We refuse to do anything beyond the
2397                defaults, except that we allow the WILL ECHO option,
2398                which ICS uses to turn off password echoing when we are
2399                directly connected to it.  We reject this option
2400                if localLineEditing mode is on (always on in xboard)
2401                and we are talking to port 23, which might be a real
2402                telnet server that will try to keep WILL ECHO on permanently.
2403              */
2404             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406                 unsigned char option;
2407                 oldi = i;
2408                 switch ((unsigned char) buf[++i]) {
2409                   case TN_WILL:
2410                     if (appData.debugMode)
2411                       fprintf(debugFP, "\n<WILL ");
2412                     switch (option = (unsigned char) buf[++i]) {
2413                       case TN_ECHO:
2414                         if (appData.debugMode)
2415                           fprintf(debugFP, "ECHO ");
2416                         /* Reply only if this is a change, according
2417                            to the protocol rules. */
2418                         if (remoteEchoOption) break;
2419                         if (appData.localLineEditing &&
2420                             atoi(appData.icsPort) == TN_PORT) {
2421                             TelnetRequest(TN_DONT, TN_ECHO);
2422                         } else {
2423                             EchoOff();
2424                             TelnetRequest(TN_DO, TN_ECHO);
2425                             remoteEchoOption = TRUE;
2426                         }
2427                         break;
2428                       default:
2429                         if (appData.debugMode)
2430                           fprintf(debugFP, "%d ", option);
2431                         /* Whatever this is, we don't want it. */
2432                         TelnetRequest(TN_DONT, option);
2433                         break;
2434                     }
2435                     break;
2436                   case TN_WONT:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<WONT ");
2439                     switch (option = (unsigned char) buf[++i]) {
2440                       case TN_ECHO:
2441                         if (appData.debugMode)
2442                           fprintf(debugFP, "ECHO ");
2443                         /* Reply only if this is a change, according
2444                            to the protocol rules. */
2445                         if (!remoteEchoOption) break;
2446                         EchoOn();
2447                         TelnetRequest(TN_DONT, TN_ECHO);
2448                         remoteEchoOption = FALSE;
2449                         break;
2450                       default:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "%d ", (unsigned char) option);
2453                         /* Whatever this is, it must already be turned
2454                            off, because we never agree to turn on
2455                            anything non-default, so according to the
2456                            protocol rules, we don't reply. */
2457                         break;
2458                     }
2459                     break;
2460                   case TN_DO:
2461                     if (appData.debugMode)
2462                       fprintf(debugFP, "\n<DO ");
2463                     switch (option = (unsigned char) buf[++i]) {
2464                       default:
2465                         /* Whatever this is, we refuse to do it. */
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "%d ", option);
2468                         TelnetRequest(TN_WONT, option);
2469                         break;
2470                     }
2471                     break;
2472                   case TN_DONT:
2473                     if (appData.debugMode)
2474                       fprintf(debugFP, "\n<DONT ");
2475                     switch (option = (unsigned char) buf[++i]) {
2476                       default:
2477                         if (appData.debugMode)
2478                           fprintf(debugFP, "%d ", option);
2479                         /* Whatever this is, we are already not doing
2480                            it, because we never agree to do anything
2481                            non-default, so according to the protocol
2482                            rules, we don't reply. */
2483                         break;
2484                     }
2485                     break;
2486                   case TN_IAC:
2487                     if (appData.debugMode)
2488                       fprintf(debugFP, "\n<IAC ");
2489                     /* Doubled IAC; pass it through */
2490                     i--;
2491                     break;
2492                   default:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495                     /* Drop all other telnet commands on the floor */
2496                     break;
2497                 }
2498                 if (oldi > next_out)
2499                   SendToPlayer(&buf[next_out], oldi - next_out);
2500                 if (++i > next_out)
2501                   next_out = i;
2502                 continue;
2503             }
2504                 
2505             /* OK, this at least will *usually* work */
2506             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507                 loggedOn = TRUE;
2508             }
2509             
2510             if (loggedOn && !intfSet) {
2511                 if (ics_type == ICS_ICC) {
2512                   sprintf(str,
2513                           "/set-quietly interface %s\n/set-quietly style 12\n",
2514                           programVersion);
2515                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2517                 } else if (ics_type == ICS_CHESSNET) {
2518                   sprintf(str, "/style 12\n");
2519                 } else {
2520                   strcpy(str, "alias $ @\n$set interface ");
2521                   strcat(str, programVersion);
2522                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2525 #ifdef WIN32
2526                   strcat(str, "$iset nohighlight 1\n");
2527 #endif
2528                   strcat(str, "$iset lock 1\n$style 12\n");
2529                 }
2530                 SendToICS(str);
2531                 NotifyFrontendLogin();
2532                 intfSet = TRUE;
2533             }
2534
2535             if (started == STARTED_COMMENT) {
2536                 /* Accumulate characters in comment */
2537                 parse[parse_pos++] = buf[i];
2538                 if (buf[i] == '\n') {
2539                     parse[parse_pos] = NULLCHAR;
2540                     if(chattingPartner>=0) {
2541                         char mess[MSG_SIZ];
2542                         sprintf(mess, "%s%s", talker, parse);
2543                         OutputChatMessage(chattingPartner, mess);
2544                         chattingPartner = -1;
2545                         next_out = i+1; // [HGM] suppress printing in ICS window
2546                     } else
2547                     if(!suppressKibitz) // [HGM] kibitz
2548                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2549                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2550                         int nrDigit = 0, nrAlph = 0, j;
2551                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2552                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2553                         parse[parse_pos] = NULLCHAR;
2554                         // try to be smart: if it does not look like search info, it should go to
2555                         // ICS interaction window after all, not to engine-output window.
2556                         for(j=0; j<parse_pos; j++) { // count letters and digits
2557                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2558                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2559                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2560                         }
2561                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2562                             int depth=0; float score;
2563                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2564                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2565                                 pvInfoList[forwardMostMove-1].depth = depth;
2566                                 pvInfoList[forwardMostMove-1].score = 100*score;
2567                             }
2568                             OutputKibitz(suppressKibitz, parse);
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                         next_out = i+1; // [HGM] suppress printing in ICS window
2575                     }
2576                     started = STARTED_NONE;
2577                 } else {
2578                     /* Don't match patterns against characters in comment */
2579                     i++;
2580                     continue;
2581                 }
2582             }
2583             if (started == STARTED_CHATTER) {
2584                 if (buf[i] != '\n') {
2585                     /* Don't match patterns against characters in chatter */
2586                     i++;
2587                     continue;
2588                 }
2589                 started = STARTED_NONE;
2590                 if(suppressKibitz) next_out = i+1;
2591             }
2592
2593             /* Kludge to deal with rcmd protocol */
2594             if (firstTime && looking_at(buf, &i, "\001*")) {
2595                 DisplayFatalError(&buf[1], 0, 1);
2596                 continue;
2597             } else {
2598                 firstTime = FALSE;
2599             }
2600
2601             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2602                 ics_type = ICS_ICC;
2603                 ics_prefix = "/";
2604                 if (appData.debugMode)
2605                   fprintf(debugFP, "ics_type %d\n", ics_type);
2606                 continue;
2607             }
2608             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2609                 ics_type = ICS_FICS;
2610                 ics_prefix = "$";
2611                 if (appData.debugMode)
2612                   fprintf(debugFP, "ics_type %d\n", ics_type);
2613                 continue;
2614             }
2615             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2616                 ics_type = ICS_CHESSNET;
2617                 ics_prefix = "/";
2618                 if (appData.debugMode)
2619                   fprintf(debugFP, "ics_type %d\n", ics_type);
2620                 continue;
2621             }
2622
2623             if (!loggedOn &&
2624                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2625                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2626                  looking_at(buf, &i, "will be \"*\""))) {
2627               strcpy(ics_handle, star_match[0]);
2628               continue;
2629             }
2630
2631             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2632               char buf[MSG_SIZ];
2633               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2634               DisplayIcsInteractionTitle(buf);
2635               have_set_title = TRUE;
2636             }
2637
2638             /* skip finger notes */
2639             if (started == STARTED_NONE &&
2640                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2641                  (buf[i] == '1' && buf[i+1] == '0')) &&
2642                 buf[i+2] == ':' && buf[i+3] == ' ') {
2643               started = STARTED_CHATTER;
2644               i += 3;
2645               continue;
2646             }
2647
2648             oldi = i;
2649             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2650             if(appData.seekGraph) {
2651                 if(soughtPending && MatchSoughtLine(buf+i)) {
2652                     i = strstr(buf+i, "rated") - buf;
2653                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2654                     next_out = leftover_start = i;
2655                     started = STARTED_CHATTER;
2656                     suppressKibitz = TRUE;
2657                     continue;
2658                 }
2659                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2660                         && looking_at(buf, &i, "* ads displayed")) {
2661                     soughtPending = FALSE;
2662                     seekGraphUp = TRUE;
2663                     DrawSeekGraph();
2664                     continue;
2665                 }
2666                 if(appData.autoRefresh) {
2667                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2668                         int s = (ics_type == ICS_ICC); // ICC format differs
2669                         if(seekGraphUp)
2670                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2671                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2672                         looking_at(buf, &i, "*% "); // eat prompt
2673                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2674                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = i; // suppress
2676                         continue;
2677                     }
2678                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2679                         char *p = star_match[0];
2680                         while(*p) {
2681                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2682                             while(*p && *p++ != ' '); // next
2683                         }
2684                         looking_at(buf, &i, "*% "); // eat prompt
2685                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                         next_out = i;
2687                         continue;
2688                     }
2689                 }
2690             }
2691
2692             /* skip formula vars */
2693             if (started == STARTED_NONE &&
2694                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2695               started = STARTED_CHATTER;
2696               i += 3;
2697               continue;
2698             }
2699
2700             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2701             if (appData.autoKibitz && started == STARTED_NONE && 
2702                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2703                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2704                 if(looking_at(buf, &i, "* kibitzes: ") &&
2705                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2706                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2707                         suppressKibitz = TRUE;
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2711                                 && (gameMode == IcsPlayingWhite)) ||
2712                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2713                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2714                             started = STARTED_CHATTER; // own kibitz we simply discard
2715                         else {
2716                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2717                             parse_pos = 0; parse[0] = NULLCHAR;
2718                             savingComment = TRUE;
2719                             suppressKibitz = gameMode != IcsObserving ? 2 :
2720                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2721                         } 
2722                         continue;
2723                 } else
2724                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2725                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2726                          && atoi(star_match[0])) {
2727                     // suppress the acknowledgements of our own autoKibitz
2728                     char *p;
2729                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2731                     SendToPlayer(star_match[0], strlen(star_match[0]));
2732                     if(looking_at(buf, &i, "*% ")) // eat prompt
2733                         suppressKibitz = FALSE;
2734                     next_out = i;
2735                     continue;
2736                 }
2737             } // [HGM] kibitz: end of patch
2738
2739             // [HGM] chat: intercept tells by users for which we have an open chat window
2740             channel = -1;
2741             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2742                                            looking_at(buf, &i, "* whispers:") ||
2743                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2744                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2745                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2746                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2747                 int p;
2748                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2749                 chattingPartner = -1;
2750
2751                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2752                 for(p=0; p<MAX_CHAT; p++) {
2753                     if(channel == atoi(chatPartner[p])) {
2754                     talker[0] = '['; strcat(talker, "] ");
2755                     chattingPartner = p; break;
2756                     }
2757                 } else
2758                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2759                 for(p=0; p<MAX_CHAT; p++) {
2760                     if(!strcmp("WHISPER", chatPartner[p])) {
2761                         talker[0] = '['; strcat(talker, "] ");
2762                         chattingPartner = p; break;
2763                     }
2764                 }
2765                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2766                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2767                     talker[0] = 0;
2768                     chattingPartner = p; break;
2769                 }
2770                 if(chattingPartner<0) i = oldi; else {
2771                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2772                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2773                     started = STARTED_COMMENT;
2774                     parse_pos = 0; parse[0] = NULLCHAR;
2775                     savingComment = 3 + chattingPartner; // counts as TRUE
2776                     suppressKibitz = TRUE;
2777                     continue;
2778                 }
2779             } // [HGM] chat: end of patch
2780
2781             if (appData.zippyTalk || appData.zippyPlay) {
2782                 /* [DM] Backup address for color zippy lines */
2783                 backup = i;
2784 #if ZIPPY
2785        #ifdef WIN32
2786                if (loggedOn == TRUE)
2787                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2788                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2789        #else
2790                 if (ZippyControl(buf, &i) ||
2791                     ZippyConverse(buf, &i) ||
2792                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2793                       loggedOn = TRUE;
2794                       if (!appData.colorize) continue;
2795                 }
2796        #endif
2797 #endif
2798             } // [DM] 'else { ' deleted
2799                 if (
2800                     /* Regular tells and says */
2801                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2802                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2803                     looking_at(buf, &i, "* says: ") ||
2804                     /* Don't color "message" or "messages" output */
2805                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2806                     looking_at(buf, &i, "*. * at *:*: ") ||
2807                     looking_at(buf, &i, "--* (*:*): ") ||
2808                     /* Message notifications (same color as tells) */
2809                     looking_at(buf, &i, "* has left a message ") ||
2810                     looking_at(buf, &i, "* just sent you a message:\n") ||
2811                     /* Whispers and kibitzes */
2812                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2813                     looking_at(buf, &i, "* kibitzes: ") ||
2814                     /* Channel tells */
2815                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2816
2817                   if (tkind == 1 && strchr(star_match[0], ':')) {
2818                       /* Avoid "tells you:" spoofs in channels */
2819                      tkind = 3;
2820                   }
2821                   if (star_match[0][0] == NULLCHAR ||
2822                       strchr(star_match[0], ' ') ||
2823                       (tkind == 3 && strchr(star_match[1], ' '))) {
2824                     /* Reject bogus matches */
2825                     i = oldi;
2826                   } else {
2827                     if (appData.colorize) {
2828                       if (oldi > next_out) {
2829                         SendToPlayer(&buf[next_out], oldi - next_out);
2830                         next_out = oldi;
2831                       }
2832                       switch (tkind) {
2833                       case 1:
2834                         Colorize(ColorTell, FALSE);
2835                         curColor = ColorTell;
2836                         break;
2837                       case 2:
2838                         Colorize(ColorKibitz, FALSE);
2839                         curColor = ColorKibitz;
2840                         break;
2841                       case 3:
2842                         p = strrchr(star_match[1], '(');
2843                         if (p == NULL) {
2844                           p = star_match[1];
2845                         } else {
2846                           p++;
2847                         }
2848                         if (atoi(p) == 1) {
2849                           Colorize(ColorChannel1, FALSE);
2850                           curColor = ColorChannel1;
2851                         } else {
2852                           Colorize(ColorChannel, FALSE);
2853                           curColor = ColorChannel;
2854                         }
2855                         break;
2856                       case 5:
2857                         curColor = ColorNormal;
2858                         break;
2859                       }
2860                     }
2861                     if (started == STARTED_NONE && appData.autoComment &&
2862                         (gameMode == IcsObserving ||
2863                          gameMode == IcsPlayingWhite ||
2864                          gameMode == IcsPlayingBlack)) {
2865                       parse_pos = i - oldi;
2866                       memcpy(parse, &buf[oldi], parse_pos);
2867                       parse[parse_pos] = NULLCHAR;
2868                       started = STARTED_COMMENT;
2869                       savingComment = TRUE;
2870                     } else {
2871                       started = STARTED_CHATTER;
2872                       savingComment = FALSE;
2873                     }
2874                     loggedOn = TRUE;
2875                     continue;
2876                   }
2877                 }
2878
2879                 if (looking_at(buf, &i, "* s-shouts: ") ||
2880                     looking_at(buf, &i, "* c-shouts: ")) {
2881                     if (appData.colorize) {
2882                         if (oldi > next_out) {
2883                             SendToPlayer(&buf[next_out], oldi - next_out);
2884                             next_out = oldi;
2885                         }
2886                         Colorize(ColorSShout, FALSE);
2887                         curColor = ColorSShout;
2888                     }
2889                     loggedOn = TRUE;
2890                     started = STARTED_CHATTER;
2891                     continue;
2892                 }
2893
2894                 if (looking_at(buf, &i, "--->")) {
2895                     loggedOn = TRUE;
2896                     continue;
2897                 }
2898
2899                 if (looking_at(buf, &i, "* shouts: ") ||
2900                     looking_at(buf, &i, "--> ")) {
2901                     if (appData.colorize) {
2902                         if (oldi > next_out) {
2903                             SendToPlayer(&buf[next_out], oldi - next_out);
2904                             next_out = oldi;
2905                         }
2906                         Colorize(ColorShout, FALSE);
2907                         curColor = ColorShout;
2908                     }
2909                     loggedOn = TRUE;
2910                     started = STARTED_CHATTER;
2911                     continue;
2912                 }
2913
2914                 if (looking_at( buf, &i, "Challenge:")) {
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorChallenge, FALSE);
2921                         curColor = ColorChallenge;
2922                     }
2923                     loggedOn = TRUE;
2924                     continue;
2925                 }
2926
2927                 if (looking_at(buf, &i, "* offers you") ||
2928                     looking_at(buf, &i, "* offers to be") ||
2929                     looking_at(buf, &i, "* would like to") ||
2930                     looking_at(buf, &i, "* requests to") ||
2931                     looking_at(buf, &i, "Your opponent offers") ||
2932                     looking_at(buf, &i, "Your opponent requests")) {
2933
2934                     if (appData.colorize) {
2935                         if (oldi > next_out) {
2936                             SendToPlayer(&buf[next_out], oldi - next_out);
2937                             next_out = oldi;
2938                         }
2939                         Colorize(ColorRequest, FALSE);
2940                         curColor = ColorRequest;
2941                     }
2942                     continue;
2943                 }
2944
2945                 if (looking_at(buf, &i, "* (*) seeking")) {
2946                     if (appData.colorize) {
2947                         if (oldi > next_out) {
2948                             SendToPlayer(&buf[next_out], oldi - next_out);
2949                             next_out = oldi;
2950                         }
2951                         Colorize(ColorSeek, FALSE);
2952                         curColor = ColorSeek;
2953                     }
2954                     continue;
2955             }
2956
2957             if (looking_at(buf, &i, "\\   ")) {
2958                 if (prevColor != ColorNormal) {
2959                     if (oldi > next_out) {
2960                         SendToPlayer(&buf[next_out], oldi - next_out);
2961                         next_out = oldi;
2962                     }
2963                     Colorize(prevColor, TRUE);
2964                     curColor = prevColor;
2965                 }
2966                 if (savingComment) {
2967                     parse_pos = i - oldi;
2968                     memcpy(parse, &buf[oldi], parse_pos);
2969                     parse[parse_pos] = NULLCHAR;
2970                     started = STARTED_COMMENT;
2971                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2972                         chattingPartner = savingComment - 3; // kludge to remember the box
2973                 } else {
2974                     started = STARTED_CHATTER;
2975                 }
2976                 continue;
2977             }
2978
2979             if (looking_at(buf, &i, "Black Strength :") ||
2980                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2981                 looking_at(buf, &i, "<10>") ||
2982                 looking_at(buf, &i, "#@#")) {
2983                 /* Wrong board style */
2984                 loggedOn = TRUE;
2985                 SendToICS(ics_prefix);
2986                 SendToICS("set style 12\n");
2987                 SendToICS(ics_prefix);
2988                 SendToICS("refresh\n");
2989                 continue;
2990             }
2991             
2992             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2993                 ICSInitScript();
2994                 have_sent_ICS_logon = 1;
2995                 continue;
2996             }
2997               
2998             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2999                 (looking_at(buf, &i, "\n<12> ") ||
3000                  looking_at(buf, &i, "<12> "))) {
3001                 loggedOn = TRUE;
3002                 if (oldi > next_out) {
3003                     SendToPlayer(&buf[next_out], oldi - next_out);
3004                 }
3005                 next_out = i;
3006                 started = STARTED_BOARD;
3007                 parse_pos = 0;
3008                 continue;
3009             }
3010
3011             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3012                 looking_at(buf, &i, "<b1> ")) {
3013                 if (oldi > next_out) {
3014                     SendToPlayer(&buf[next_out], oldi - next_out);
3015                 }
3016                 next_out = i;
3017                 started = STARTED_HOLDINGS;
3018                 parse_pos = 0;
3019                 continue;
3020             }
3021
3022             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3023                 loggedOn = TRUE;
3024                 /* Header for a move list -- first line */
3025
3026                 switch (ics_getting_history) {
3027                   case H_FALSE:
3028                     switch (gameMode) {
3029                       case IcsIdle:
3030                       case BeginningOfGame:
3031                         /* User typed "moves" or "oldmoves" while we
3032                            were idle.  Pretend we asked for these
3033                            moves and soak them up so user can step
3034                            through them and/or save them.
3035                            */
3036                         Reset(FALSE, TRUE);
3037                         gameMode = IcsObserving;
3038                         ModeHighlight();
3039                         ics_gamenum = -1;
3040                         ics_getting_history = H_GOT_UNREQ_HEADER;
3041                         break;
3042                       case EditGame: /*?*/
3043                       case EditPosition: /*?*/
3044                         /* Should above feature work in these modes too? */
3045                         /* For now it doesn't */
3046                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3047                         break;
3048                       default:
3049                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3050                         break;
3051                     }
3052                     break;
3053                   case H_REQUESTED:
3054                     /* Is this the right one? */
3055                     if (gameInfo.white && gameInfo.black &&
3056                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3057                         strcmp(gameInfo.black, star_match[2]) == 0) {
3058                         /* All is well */
3059                         ics_getting_history = H_GOT_REQ_HEADER;
3060                     }
3061                     break;
3062                   case H_GOT_REQ_HEADER:
3063                   case H_GOT_UNREQ_HEADER:
3064                   case H_GOT_UNWANTED_HEADER:
3065                   case H_GETTING_MOVES:
3066                     /* Should not happen */
3067                     DisplayError(_("Error gathering move list: two headers"), 0);
3068                     ics_getting_history = H_FALSE;
3069                     break;
3070                 }
3071
3072                 /* Save player ratings into gameInfo if needed */
3073                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3074                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3075                     (gameInfo.whiteRating == -1 ||
3076                      gameInfo.blackRating == -1)) {
3077
3078                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3079                     gameInfo.blackRating = string_to_rating(star_match[3]);
3080                     if (appData.debugMode)
3081                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3082                               gameInfo.whiteRating, gameInfo.blackRating);
3083                 }
3084                 continue;
3085             }
3086
3087             if (looking_at(buf, &i,
3088               "* * match, initial time: * minute*, increment: * second")) {
3089                 /* Header for a move list -- second line */
3090                 /* Initial board will follow if this is a wild game */
3091                 if (gameInfo.event != NULL) free(gameInfo.event);
3092                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3093                 gameInfo.event = StrSave(str);
3094                 /* [HGM] we switched variant. Translate boards if needed. */
3095                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3096                 continue;
3097             }
3098
3099             if (looking_at(buf, &i, "Move  ")) {
3100                 /* Beginning of a move list */
3101                 switch (ics_getting_history) {
3102                   case H_FALSE:
3103                     /* Normally should not happen */
3104                     /* Maybe user hit reset while we were parsing */
3105                     break;
3106                   case H_REQUESTED:
3107                     /* Happens if we are ignoring a move list that is not
3108                      * the one we just requested.  Common if the user
3109                      * tries to observe two games without turning off
3110                      * getMoveList */
3111                     break;
3112                   case H_GETTING_MOVES:
3113                     /* Should not happen */
3114                     DisplayError(_("Error gathering move list: nested"), 0);
3115                     ics_getting_history = H_FALSE;
3116                     break;
3117                   case H_GOT_REQ_HEADER:
3118                     ics_getting_history = H_GETTING_MOVES;
3119                     started = STARTED_MOVES;
3120                     parse_pos = 0;
3121                     if (oldi > next_out) {
3122                         SendToPlayer(&buf[next_out], oldi - next_out);
3123                     }
3124                     break;
3125                   case H_GOT_UNREQ_HEADER:
3126                     ics_getting_history = H_GETTING_MOVES;
3127                     started = STARTED_MOVES_NOHIDE;
3128                     parse_pos = 0;
3129                     break;
3130                   case H_GOT_UNWANTED_HEADER:
3131                     ics_getting_history = H_FALSE;
3132                     break;
3133                 }
3134                 continue;
3135             }                           
3136             
3137             if (looking_at(buf, &i, "% ") ||
3138                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3139                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3140                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3141                     soughtPending = FALSE;
3142                     seekGraphUp = TRUE;
3143                     DrawSeekGraph();
3144                 }
3145                 if(suppressKibitz) next_out = i;
3146                 savingComment = FALSE;
3147                 suppressKibitz = 0;
3148                 switch (started) {
3149                   case STARTED_MOVES:
3150                   case STARTED_MOVES_NOHIDE:
3151                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3152                     parse[parse_pos + i - oldi] = NULLCHAR;
3153                     ParseGameHistory(parse);
3154 #if ZIPPY
3155                     if (appData.zippyPlay && first.initDone) {
3156                         FeedMovesToProgram(&first, forwardMostMove);
3157                         if (gameMode == IcsPlayingWhite) {
3158                             if (WhiteOnMove(forwardMostMove)) {
3159                                 if (first.sendTime) {
3160                                   if (first.useColors) {
3161                                     SendToProgram("black\n", &first); 
3162                                   }
3163                                   SendTimeRemaining(&first, TRUE);
3164                                 }
3165                                 if (first.useColors) {
3166                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3167                                 }
3168                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3169                                 first.maybeThinking = TRUE;
3170                             } else {
3171                                 if (first.usePlayother) {
3172                                   if (first.sendTime) {
3173                                     SendTimeRemaining(&first, TRUE);
3174                                   }
3175                                   SendToProgram("playother\n", &first);
3176                                   firstMove = FALSE;
3177                                 } else {
3178                                   firstMove = TRUE;
3179                                 }
3180                             }
3181                         } else if (gameMode == IcsPlayingBlack) {
3182                             if (!WhiteOnMove(forwardMostMove)) {
3183                                 if (first.sendTime) {
3184                                   if (first.useColors) {
3185                                     SendToProgram("white\n", &first);
3186                                   }
3187                                   SendTimeRemaining(&first, FALSE);
3188                                 }
3189                                 if (first.useColors) {
3190                                   SendToProgram("black\n", &first);
3191                                 }
3192                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3193                                 first.maybeThinking = TRUE;
3194                             } else {
3195                                 if (first.usePlayother) {
3196                                   if (first.sendTime) {
3197                                     SendTimeRemaining(&first, FALSE);
3198                                   }
3199                                   SendToProgram("playother\n", &first);
3200                                   firstMove = FALSE;
3201                                 } else {
3202                                   firstMove = TRUE;
3203                                 }
3204                             }
3205                         }                       
3206                     }
3207 #endif
3208                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3209                         /* Moves came from oldmoves or moves command
3210                            while we weren't doing anything else.
3211                            */
3212                         currentMove = forwardMostMove;
3213                         ClearHighlights();/*!!could figure this out*/
3214                         flipView = appData.flipView;
3215                         DrawPosition(TRUE, boards[currentMove]);
3216                         DisplayBothClocks();
3217                         sprintf(str, "%s vs. %s",
3218                                 gameInfo.white, gameInfo.black);
3219                         DisplayTitle(str);
3220                         gameMode = IcsIdle;
3221                     } else {
3222                         /* Moves were history of an active game */
3223                         if (gameInfo.resultDetails != NULL) {
3224                             free(gameInfo.resultDetails);
3225                             gameInfo.resultDetails = NULL;
3226                         }
3227                     }
3228                     HistorySet(parseList, backwardMostMove,
3229                                forwardMostMove, currentMove-1);
3230                     DisplayMove(currentMove - 1);
3231                     if (started == STARTED_MOVES) next_out = i;
3232                     started = STARTED_NONE;
3233                     ics_getting_history = H_FALSE;
3234                     break;
3235
3236                   case STARTED_OBSERVE:
3237                     started = STARTED_NONE;
3238                     SendToICS(ics_prefix);
3239                     SendToICS("refresh\n");
3240                     break;
3241
3242                   default:
3243                     break;
3244                 }
3245                 if(bookHit) { // [HGM] book: simulate book reply
3246                     static char bookMove[MSG_SIZ]; // a bit generous?
3247
3248                     programStats.nodes = programStats.depth = programStats.time = 
3249                     programStats.score = programStats.got_only_move = 0;
3250                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3251
3252                     strcpy(bookMove, "move ");
3253                     strcat(bookMove, bookHit);
3254                     HandleMachineMove(bookMove, &first);
3255                 }
3256                 continue;
3257             }
3258             
3259             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3260                  started == STARTED_HOLDINGS ||
3261                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3262                 /* Accumulate characters in move list or board */
3263                 parse[parse_pos++] = buf[i];
3264             }
3265             
3266             /* Start of game messages.  Mostly we detect start of game
3267                when the first board image arrives.  On some versions
3268                of the ICS, though, we need to do a "refresh" after starting
3269                to observe in order to get the current board right away. */
3270             if (looking_at(buf, &i, "Adding game * to observation list")) {
3271                 started = STARTED_OBSERVE;
3272                 continue;
3273             }
3274
3275             /* Handle auto-observe */
3276             if (appData.autoObserve &&
3277                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3278                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3279                 char *player;
3280                 /* Choose the player that was highlighted, if any. */
3281                 if (star_match[0][0] == '\033' ||
3282                     star_match[1][0] != '\033') {
3283                     player = star_match[0];
3284                 } else {
3285                     player = star_match[2];
3286                 }
3287                 sprintf(str, "%sobserve %s\n",
3288                         ics_prefix, StripHighlightAndTitle(player));
3289                 SendToICS(str);
3290
3291                 /* Save ratings from notify string */
3292                 strcpy(player1Name, star_match[0]);
3293                 player1Rating = string_to_rating(star_match[1]);
3294                 strcpy(player2Name, star_match[2]);
3295                 player2Rating = string_to_rating(star_match[3]);
3296
3297                 if (appData.debugMode)
3298                   fprintf(debugFP, 
3299                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3300                           player1Name, player1Rating,
3301                           player2Name, player2Rating);
3302
3303                 continue;
3304             }
3305
3306             /* Deal with automatic examine mode after a game,
3307                and with IcsObserving -> IcsExamining transition */
3308             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3309                 looking_at(buf, &i, "has made you an examiner of game *")) {
3310
3311                 int gamenum = atoi(star_match[0]);
3312                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3313                     gamenum == ics_gamenum) {
3314                     /* We were already playing or observing this game;
3315                        no need to refetch history */
3316                     gameMode = IcsExamining;
3317                     if (pausing) {
3318                         pauseExamForwardMostMove = forwardMostMove;
3319                     } else if (currentMove < forwardMostMove) {
3320                         ForwardInner(forwardMostMove);
3321                     }
3322                 } else {
3323                     /* I don't think this case really can happen */
3324                     SendToICS(ics_prefix);
3325                     SendToICS("refresh\n");
3326                 }
3327                 continue;
3328             }    
3329             
3330             /* Error messages */
3331 //          if (ics_user_moved) {
3332             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3333                 if (looking_at(buf, &i, "Illegal move") ||
3334                     looking_at(buf, &i, "Not a legal move") ||
3335                     looking_at(buf, &i, "Your king is in check") ||
3336                     looking_at(buf, &i, "It isn't your turn") ||
3337                     looking_at(buf, &i, "It is not your move")) {
3338                     /* Illegal move */
3339                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3340                         currentMove = forwardMostMove-1;
3341                         DisplayMove(currentMove - 1); /* before DMError */
3342                         DrawPosition(FALSE, boards[currentMove]);