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