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