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