Use same translation of "vs." everywhere
[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         ParseArgsFromString(buf);
884         SwapEngines(i);
885         ReplaceEngine(cps, i);
886         return;
887     }
888     p = engineName;
889     while(q = strchr(p, SLASH)) p = q+1;
890     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891     if(engineDir[0] != NULLCHAR)
892         appData.directory[i] = engineDir;
893     else if(p != engineName) { // derive directory from engine path, when not given
894         p[-1] = 0;
895         appData.directory[i] = strdup(engineName);
896         p[-1] = SLASH;
897     } else appData.directory[i] = ".";
898     if(params[0]) {
899         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900         snprintf(command, MSG_SIZ, "%s %s", p, params);
901         p = command;
902     }
903     appData.chessProgram[i] = strdup(p);
904     appData.isUCI[i] = isUCI;
905     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906     appData.hasOwnBookUCI[i] = hasBook;
907     if(!nickName[0]) useNick = FALSE;
908     if(useNick) ASSIGN(appData.pgnName[i], nickName);
909     if(addToList) {
910         int len;
911         char quote;
912         q = firstChessProgramNames;
913         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
914         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
915         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
916                         quote, p, quote, appData.directory[i], 
917                         useNick ? " -fn \"" : "",
918                         useNick ? nickName : "",
919                         useNick ? "\"" : "",
920                         v1 ? " -firstProtocolVersion 1" : "",
921                         hasBook ? "" : " -fNoOwnBookUCI",
922                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
923                         storeVariant ? " -variant " : "",
924                         storeVariant ? VariantName(gameInfo.variant) : "");
925         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
926         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
927         if(q)   free(q);
928     }
929     ReplaceEngine(cps, i);
930 }
931
932 void
933 InitTimeControls ()
934 {
935     int matched, min, sec;
936     /*
937      * Parse timeControl resource
938      */
939     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
940                           appData.movesPerSession)) {
941         char buf[MSG_SIZ];
942         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
943         DisplayFatalError(buf, 0, 2);
944     }
945
946     /*
947      * Parse searchTime resource
948      */
949     if (*appData.searchTime != NULLCHAR) {
950         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
951         if (matched == 1) {
952             searchTime = min * 60;
953         } else if (matched == 2) {
954             searchTime = min * 60 + sec;
955         } else {
956             char buf[MSG_SIZ];
957             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
958             DisplayFatalError(buf, 0, 2);
959         }
960     }
961 }
962
963 void
964 InitBackEnd1 ()
965 {
966
967     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
968     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
969
970     GetTimeMark(&programStartTime);
971     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
972     appData.seedBase = random() + (random()<<15);
973     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
974
975     ClearProgramStats();
976     programStats.ok_to_send = 1;
977     programStats.seen_stat = 0;
978
979     /*
980      * Initialize game list
981      */
982     ListNew(&gameList);
983
984
985     /*
986      * Internet chess server status
987      */
988     if (appData.icsActive) {
989         appData.matchMode = FALSE;
990         appData.matchGames = 0;
991 #if ZIPPY
992         appData.noChessProgram = !appData.zippyPlay;
993 #else
994         appData.zippyPlay = FALSE;
995         appData.zippyTalk = FALSE;
996         appData.noChessProgram = TRUE;
997 #endif
998         if (*appData.icsHelper != NULLCHAR) {
999             appData.useTelnet = TRUE;
1000             appData.telnetProgram = appData.icsHelper;
1001         }
1002     } else {
1003         appData.zippyTalk = appData.zippyPlay = FALSE;
1004     }
1005
1006     /* [AS] Initialize pv info list [HGM] and game state */
1007     {
1008         int i, j;
1009
1010         for( i=0; i<=framePtr; i++ ) {
1011             pvInfoList[i].depth = -1;
1012             boards[i][EP_STATUS] = EP_NONE;
1013             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1014         }
1015     }
1016
1017     InitTimeControls();
1018
1019     /* [AS] Adjudication threshold */
1020     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1021
1022     InitEngine(&first, 0);
1023     InitEngine(&second, 1);
1024     CommonEngineInit();
1025
1026     pairing.which = "pairing"; // pairing engine
1027     pairing.pr = NoProc;
1028     pairing.isr = NULL;
1029     pairing.program = appData.pairingEngine;
1030     pairing.host = "localhost";
1031     pairing.dir = ".";
1032
1033     if (appData.icsActive) {
1034         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1035     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036         appData.clockMode = FALSE;
1037         first.sendTime = second.sendTime = 0;
1038     }
1039
1040 #if ZIPPY
1041     /* Override some settings from environment variables, for backward
1042        compatibility.  Unfortunately it's not feasible to have the env
1043        vars just set defaults, at least in xboard.  Ugh.
1044     */
1045     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046       ZippyInit();
1047     }
1048 #endif
1049
1050     if (!appData.icsActive) {
1051       char buf[MSG_SIZ];
1052       int len;
1053
1054       /* Check for variants that are supported only in ICS mode,
1055          or not at all.  Some that are accepted here nevertheless
1056          have bugs; see comments below.
1057       */
1058       VariantClass variant = StringToVariant(appData.variant);
1059       switch (variant) {
1060       case VariantBughouse:     /* need four players and two boards */
1061       case VariantKriegspiel:   /* need to hide pieces and move details */
1062         /* case VariantFischeRandom: (Fabien: moved below) */
1063         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064         if( (len >= MSG_SIZ) && appData.debugMode )
1065           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1066
1067         DisplayFatalError(buf, 0, 2);
1068         return;
1069
1070       case VariantUnknown:
1071       case VariantLoadable:
1072       case Variant29:
1073       case Variant30:
1074       case Variant31:
1075       case Variant32:
1076       case Variant33:
1077       case Variant34:
1078       case Variant35:
1079       case Variant36:
1080       default:
1081         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082         if( (len >= MSG_SIZ) && appData.debugMode )
1083           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1084
1085         DisplayFatalError(buf, 0, 2);
1086         return;
1087
1088       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1089       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1090       case VariantGothic:     /* [HGM] should work */
1091       case VariantCapablanca: /* [HGM] should work */
1092       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1093       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1094       case VariantKnightmate: /* [HGM] should work */
1095       case VariantCylinder:   /* [HGM] untested */
1096       case VariantFalcon:     /* [HGM] untested */
1097       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098                                  offboard interposition not understood */
1099       case VariantNormal:     /* definitely works! */
1100       case VariantWildCastle: /* pieces not automatically shuffled */
1101       case VariantNoCastle:   /* pieces not automatically shuffled */
1102       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103       case VariantLosers:     /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantSuicide:    /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantGiveaway:   /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantTwoKings:   /* should work */
1110       case VariantAtomic:     /* should work except for win condition */
1111       case Variant3Check:     /* should work except for win condition */
1112       case VariantShatranj:   /* should work except for all win conditions */
1113       case VariantMakruk:     /* should work except for draw countdown */
1114       case VariantBerolina:   /* might work if TestLegality is off */
1115       case VariantCapaRandom: /* should work */
1116       case VariantJanus:      /* should work */
1117       case VariantSuper:      /* experimental */
1118       case VariantGreat:      /* experimental, requires legality testing to be off */
1119       case VariantSChess:     /* S-Chess, should work */
1120       case VariantGrand:      /* should work */
1121       case VariantSpartan:    /* should work */
1122         break;
1123       }
1124     }
1125
1126 }
1127
1128 int
1129 NextIntegerFromString (char ** str, long * value)
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int
1155 NextTimeControlFromString (char ** str, long * value)
1156 {
1157     long temp;
1158     int result = NextIntegerFromString( str, &temp );
1159
1160     if( result == 0 ) {
1161         *value = temp * 60; /* Minutes */
1162         if( **str == ':' ) {
1163             (*str)++;
1164             result = NextIntegerFromString( str, &temp );
1165             *value += temp; /* Seconds */
1166         }
1167     }
1168
1169     return result;
1170 }
1171
1172 int
1173 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1174 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1175     int result = -1, type = 0; long temp, temp2;
1176
1177     if(**str != ':') return -1; // old params remain in force!
1178     (*str)++;
1179     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1180     if( NextIntegerFromString( str, &temp ) ) return -1;
1181     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1182
1183     if(**str != '/') {
1184         /* time only: incremental or sudden-death time control */
1185         if(**str == '+') { /* increment follows; read it */
1186             (*str)++;
1187             if(**str == '!') type = *(*str)++; // Bronstein TC
1188             if(result = NextIntegerFromString( str, &temp2)) return -1;
1189             *inc = temp2 * 1000;
1190             if(**str == '.') { // read fraction of increment
1191                 char *start = ++(*str);
1192                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1193                 temp2 *= 1000;
1194                 while(start++ < *str) temp2 /= 10;
1195                 *inc += temp2;
1196             }
1197         } else *inc = 0;
1198         *moves = 0; *tc = temp * 1000; *incType = type;
1199         return 0;
1200     }
1201
1202     (*str)++; /* classical time control */
1203     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1204
1205     if(result == 0) {
1206         *moves = temp;
1207         *tc    = temp2 * 1000;
1208         *inc   = 0;
1209         *incType = type;
1210     }
1211     return result;
1212 }
1213
1214 int
1215 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1216 {   /* [HGM] get time to add from the multi-session time-control string */
1217     int incType, moves=1; /* kludge to force reading of first session */
1218     long time, increment;
1219     char *s = tcString;
1220
1221     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1222     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1223     do {
1224         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1225         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1226         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1227         if(movenr == -1) return time;    /* last move before new session     */
1228         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1229         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1230         if(!moves) return increment;     /* current session is incremental   */
1231         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1232     } while(movenr >= -1);               /* try again for next session       */
1233
1234     return 0; // no new time quota on this move
1235 }
1236
1237 int
1238 ParseTimeControl (char *tc, float ti, int mps)
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2 ()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex (int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition (int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame (int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent (int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish ()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void
1718 EscapeExpand (char *p, char *q)
1719 {       // [HGM] initstring: routine to shape up string arguments
1720         while(*p++ = *q++) if(p[-1] == '\\')
1721             switch(*q++) {
1722                 case 'n': p[-1] = '\n'; break;
1723                 case 'r': p[-1] = '\r'; break;
1724                 case 't': p[-1] = '\t'; break;
1725                 case '\\': p[-1] = '\\'; break;
1726                 case 0: *p = 0; return;
1727                 default: p[-1] = q[-1]; break;
1728             }
1729 }
1730
1731 void
1732 show_bytes (FILE *fp, char *buf, int count)
1733 {
1734     while (count--) {
1735         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736             fprintf(fp, "\\%03o", *buf & 0xff);
1737         } else {
1738             putc(*buf, fp);
1739         }
1740         buf++;
1741     }
1742     fflush(fp);
1743 }
1744
1745 /* Returns an errno value */
1746 int
1747 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1748 {
1749     char buf[8192], *p, *q, *buflim;
1750     int left, newcount, outcount;
1751
1752     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1753         *appData.gateway != NULLCHAR) {
1754         if (appData.debugMode) {
1755             fprintf(debugFP, ">ICS: ");
1756             show_bytes(debugFP, message, count);
1757             fprintf(debugFP, "\n");
1758         }
1759         return OutputToProcess(pr, message, count, outError);
1760     }
1761
1762     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1763     p = message;
1764     q = buf;
1765     left = count;
1766     newcount = 0;
1767     while (left) {
1768         if (q >= buflim) {
1769             if (appData.debugMode) {
1770                 fprintf(debugFP, ">ICS: ");
1771                 show_bytes(debugFP, buf, newcount);
1772                 fprintf(debugFP, "\n");
1773             }
1774             outcount = OutputToProcess(pr, buf, newcount, outError);
1775             if (outcount < newcount) return -1; /* to be sure */
1776             q = buf;
1777             newcount = 0;
1778         }
1779         if (*p == '\n') {
1780             *q++ = '\r';
1781             newcount++;
1782         } else if (((unsigned char) *p) == TN_IAC) {
1783             *q++ = (char) TN_IAC;
1784             newcount ++;
1785         }
1786         *q++ = *p++;
1787         newcount++;
1788         left--;
1789     }
1790     if (appData.debugMode) {
1791         fprintf(debugFP, ">ICS: ");
1792         show_bytes(debugFP, buf, newcount);
1793         fprintf(debugFP, "\n");
1794     }
1795     outcount = OutputToProcess(pr, buf, newcount, outError);
1796     if (outcount < newcount) return -1; /* to be sure */
1797     return count;
1798 }
1799
1800 void
1801 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1802 {
1803     int outError, outCount;
1804     static int gotEof = 0;
1805
1806     /* Pass data read from player on to ICS */
1807     if (count > 0) {
1808         gotEof = 0;
1809         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1810         if (outCount < count) {
1811             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1812         }
1813     } else if (count < 0) {
1814         RemoveInputSource(isr);
1815         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1816     } else if (gotEof++ > 0) {
1817         RemoveInputSource(isr);
1818         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1819     }
1820 }
1821
1822 void
1823 KeepAlive ()
1824 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1825     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1826     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1827     SendToICS("date\n");
1828     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1829 }
1830
1831 /* added routine for printf style output to ics */
1832 void
1833 ics_printf (char *format, ...)
1834 {
1835     char buffer[MSG_SIZ];
1836     va_list args;
1837
1838     va_start(args, format);
1839     vsnprintf(buffer, sizeof(buffer), format, args);
1840     buffer[sizeof(buffer)-1] = '\0';
1841     SendToICS(buffer);
1842     va_end(args);
1843 }
1844
1845 void
1846 SendToICS (char *s)
1847 {
1848     int count, outCount, outError;
1849
1850     if (icsPR == NoProc) return;
1851
1852     count = strlen(s);
1853     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1854     if (outCount < count) {
1855         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1856     }
1857 }
1858
1859 /* This is used for sending logon scripts to the ICS. Sending
1860    without a delay causes problems when using timestamp on ICC
1861    (at least on my machine). */
1862 void
1863 SendToICSDelayed (char *s, long msdelay)
1864 {
1865     int count, outCount, outError;
1866
1867     if (icsPR == NoProc) return;
1868
1869     count = strlen(s);
1870     if (appData.debugMode) {
1871         fprintf(debugFP, ">ICS: ");
1872         show_bytes(debugFP, s, count);
1873         fprintf(debugFP, "\n");
1874     }
1875     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1876                                       msdelay);
1877     if (outCount < count) {
1878         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879     }
1880 }
1881
1882
1883 /* Remove all highlighting escape sequences in s
1884    Also deletes any suffix starting with '('
1885    */
1886 char *
1887 StripHighlightAndTitle (char *s)
1888 {
1889     static char retbuf[MSG_SIZ];
1890     char *p = retbuf;
1891
1892     while (*s != NULLCHAR) {
1893         while (*s == '\033') {
1894             while (*s != NULLCHAR && !isalpha(*s)) s++;
1895             if (*s != NULLCHAR) s++;
1896         }
1897         while (*s != NULLCHAR && *s != '\033') {
1898             if (*s == '(' || *s == '[') {
1899                 *p = NULLCHAR;
1900                 return retbuf;
1901             }
1902             *p++ = *s++;
1903         }
1904     }
1905     *p = NULLCHAR;
1906     return retbuf;
1907 }
1908
1909 /* Remove all highlighting escape sequences in s */
1910 char *
1911 StripHighlight (char *s)
1912 {
1913     static char retbuf[MSG_SIZ];
1914     char *p = retbuf;
1915
1916     while (*s != NULLCHAR) {
1917         while (*s == '\033') {
1918             while (*s != NULLCHAR && !isalpha(*s)) s++;
1919             if (*s != NULLCHAR) s++;
1920         }
1921         while (*s != NULLCHAR && *s != '\033') {
1922             *p++ = *s++;
1923         }
1924     }
1925     *p = NULLCHAR;
1926     return retbuf;
1927 }
1928
1929 char *variantNames[] = VARIANT_NAMES;
1930 char *
1931 VariantName (VariantClass v)
1932 {
1933     return variantNames[v];
1934 }
1935
1936
1937 /* Identify a variant from the strings the chess servers use or the
1938    PGN Variant tag names we use. */
1939 VariantClass
1940 StringToVariant (char *e)
1941 {
1942     char *p;
1943     int wnum = -1;
1944     VariantClass v = VariantNormal;
1945     int i, found = FALSE;
1946     char buf[MSG_SIZ];
1947     int len;
1948
1949     if (!e) return v;
1950
1951     /* [HGM] skip over optional board-size prefixes */
1952     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1953         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1954         while( *e++ != '_');
1955     }
1956
1957     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1958         v = VariantNormal;
1959         found = TRUE;
1960     } else
1961     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1962       if (StrCaseStr(e, variantNames[i])) {
1963         v = (VariantClass) i;
1964         found = TRUE;
1965         break;
1966       }
1967     }
1968
1969     if (!found) {
1970       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1971           || StrCaseStr(e, "wild/fr")
1972           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1973         v = VariantFischeRandom;
1974       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1975                  (i = 1, p = StrCaseStr(e, "w"))) {
1976         p += i;
1977         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1978         if (isdigit(*p)) {
1979           wnum = atoi(p);
1980         } else {
1981           wnum = -1;
1982         }
1983         switch (wnum) {
1984         case 0: /* FICS only, actually */
1985         case 1:
1986           /* Castling legal even if K starts on d-file */
1987           v = VariantWildCastle;
1988           break;
1989         case 2:
1990         case 3:
1991         case 4:
1992           /* Castling illegal even if K & R happen to start in
1993              normal positions. */
1994           v = VariantNoCastle;
1995           break;
1996         case 5:
1997         case 7:
1998         case 8:
1999         case 10:
2000         case 11:
2001         case 12:
2002         case 13:
2003         case 14:
2004         case 15:
2005         case 18:
2006         case 19:
2007           /* Castling legal iff K & R start in normal positions */
2008           v = VariantNormal;
2009           break;
2010         case 6:
2011         case 20:
2012         case 21:
2013           /* Special wilds for position setup; unclear what to do here */
2014           v = VariantLoadable;
2015           break;
2016         case 9:
2017           /* Bizarre ICC game */
2018           v = VariantTwoKings;
2019           break;
2020         case 16:
2021           v = VariantKriegspiel;
2022           break;
2023         case 17:
2024           v = VariantLosers;
2025           break;
2026         case 22:
2027           v = VariantFischeRandom;
2028           break;
2029         case 23:
2030           v = VariantCrazyhouse;
2031           break;
2032         case 24:
2033           v = VariantBughouse;
2034           break;
2035         case 25:
2036           v = Variant3Check;
2037           break;
2038         case 26:
2039           /* Not quite the same as FICS suicide! */
2040           v = VariantGiveaway;
2041           break;
2042         case 27:
2043           v = VariantAtomic;
2044           break;
2045         case 28:
2046           v = VariantShatranj;
2047           break;
2048
2049         /* Temporary names for future ICC types.  The name *will* change in
2050            the next xboard/WinBoard release after ICC defines it. */
2051         case 29:
2052           v = Variant29;
2053           break;
2054         case 30:
2055           v = Variant30;
2056           break;
2057         case 31:
2058           v = Variant31;
2059           break;
2060         case 32:
2061           v = Variant32;
2062           break;
2063         case 33:
2064           v = Variant33;
2065           break;
2066         case 34:
2067           v = Variant34;
2068           break;
2069         case 35:
2070           v = Variant35;
2071           break;
2072         case 36:
2073           v = Variant36;
2074           break;
2075         case 37:
2076           v = VariantShogi;
2077           break;
2078         case 38:
2079           v = VariantXiangqi;
2080           break;
2081         case 39:
2082           v = VariantCourier;
2083           break;
2084         case 40:
2085           v = VariantGothic;
2086           break;
2087         case 41:
2088           v = VariantCapablanca;
2089           break;
2090         case 42:
2091           v = VariantKnightmate;
2092           break;
2093         case 43:
2094           v = VariantFairy;
2095           break;
2096         case 44:
2097           v = VariantCylinder;
2098           break;
2099         case 45:
2100           v = VariantFalcon;
2101           break;
2102         case 46:
2103           v = VariantCapaRandom;
2104           break;
2105         case 47:
2106           v = VariantBerolina;
2107           break;
2108         case 48:
2109           v = VariantJanus;
2110           break;
2111         case 49:
2112           v = VariantSuper;
2113           break;
2114         case 50:
2115           v = VariantGreat;
2116           break;
2117         case -1:
2118           /* Found "wild" or "w" in the string but no number;
2119              must assume it's normal chess. */
2120           v = VariantNormal;
2121           break;
2122         default:
2123           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2124           if( (len >= MSG_SIZ) && appData.debugMode )
2125             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2126
2127           DisplayError(buf, 0);
2128           v = VariantUnknown;
2129           break;
2130         }
2131       }
2132     }
2133     if (appData.debugMode) {
2134       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2135               e, wnum, VariantName(v));
2136     }
2137     return v;
2138 }
2139
2140 static int leftover_start = 0, leftover_len = 0;
2141 char star_match[STAR_MATCH_N][MSG_SIZ];
2142
2143 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2144    advance *index beyond it, and set leftover_start to the new value of
2145    *index; else return FALSE.  If pattern contains the character '*', it
2146    matches any sequence of characters not containing '\r', '\n', or the
2147    character following the '*' (if any), and the matched sequence(s) are
2148    copied into star_match.
2149    */
2150 int
2151 looking_at ( char *buf, int *index, char *pattern)
2152 {
2153     char *bufp = &buf[*index], *patternp = pattern;
2154     int star_count = 0;
2155     char *matchp = star_match[0];
2156
2157     for (;;) {
2158         if (*patternp == NULLCHAR) {
2159             *index = leftover_start = bufp - buf;
2160             *matchp = NULLCHAR;
2161             return TRUE;
2162         }
2163         if (*bufp == NULLCHAR) return FALSE;
2164         if (*patternp == '*') {
2165             if (*bufp == *(patternp + 1)) {
2166                 *matchp = NULLCHAR;
2167                 matchp = star_match[++star_count];
2168                 patternp += 2;
2169                 bufp++;
2170                 continue;
2171             } else if (*bufp == '\n' || *bufp == '\r') {
2172                 patternp++;
2173                 if (*patternp == NULLCHAR)
2174                   continue;
2175                 else
2176                   return FALSE;
2177             } else {
2178                 *matchp++ = *bufp++;
2179                 continue;
2180             }
2181         }
2182         if (*patternp != *bufp) return FALSE;
2183         patternp++;
2184         bufp++;
2185     }
2186 }
2187
2188 void
2189 SendToPlayer (char *data, int length)
2190 {
2191     int error, outCount;
2192     outCount = OutputToProcess(NoProc, data, length, &error);
2193     if (outCount < length) {
2194         DisplayFatalError(_("Error writing to display"), error, 1);
2195     }
2196 }
2197
2198 void
2199 PackHolding (char packed[], char *holding)
2200 {
2201     char *p = holding;
2202     char *q = packed;
2203     int runlength = 0;
2204     int curr = 9999;
2205     do {
2206         if (*p == curr) {
2207             runlength++;
2208         } else {
2209             switch (runlength) {
2210               case 0:
2211                 break;
2212               case 1:
2213                 *q++ = curr;
2214                 break;
2215               case 2:
2216                 *q++ = curr;
2217                 *q++ = curr;
2218                 break;
2219               default:
2220                 sprintf(q, "%d", runlength);
2221                 while (*q) q++;
2222                 *q++ = curr;
2223                 break;
2224             }
2225             runlength = 1;
2226             curr = *p;
2227         }
2228     } while (*p++);
2229     *q = NULLCHAR;
2230 }
2231
2232 /* Telnet protocol requests from the front end */
2233 void
2234 TelnetRequest (unsigned char ddww, unsigned char option)
2235 {
2236     unsigned char msg[3];
2237     int outCount, outError;
2238
2239     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2240
2241     if (appData.debugMode) {
2242         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2243         switch (ddww) {
2244           case TN_DO:
2245             ddwwStr = "DO";
2246             break;
2247           case TN_DONT:
2248             ddwwStr = "DONT";
2249             break;
2250           case TN_WILL:
2251             ddwwStr = "WILL";
2252             break;
2253           case TN_WONT:
2254             ddwwStr = "WONT";
2255             break;
2256           default:
2257             ddwwStr = buf1;
2258             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2259             break;
2260         }
2261         switch (option) {
2262           case TN_ECHO:
2263             optionStr = "ECHO";
2264             break;
2265           default:
2266             optionStr = buf2;
2267             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2268             break;
2269         }
2270         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2271     }
2272     msg[0] = TN_IAC;
2273     msg[1] = ddww;
2274     msg[2] = option;
2275     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2276     if (outCount < 3) {
2277         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2278     }
2279 }
2280
2281 void
2282 DoEcho ()
2283 {
2284     if (!appData.icsActive) return;
2285     TelnetRequest(TN_DO, TN_ECHO);
2286 }
2287
2288 void
2289 DontEcho ()
2290 {
2291     if (!appData.icsActive) return;
2292     TelnetRequest(TN_DONT, TN_ECHO);
2293 }
2294
2295 void
2296 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2297 {
2298     /* put the holdings sent to us by the server on the board holdings area */
2299     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2300     char p;
2301     ChessSquare piece;
2302
2303     if(gameInfo.holdingsWidth < 2)  return;
2304     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2305         return; // prevent overwriting by pre-board holdings
2306
2307     if( (int)lowestPiece >= BlackPawn ) {
2308         holdingsColumn = 0;
2309         countsColumn = 1;
2310         holdingsStartRow = BOARD_HEIGHT-1;
2311         direction = -1;
2312     } else {
2313         holdingsColumn = BOARD_WIDTH-1;
2314         countsColumn = BOARD_WIDTH-2;
2315         holdingsStartRow = 0;
2316         direction = 1;
2317     }
2318
2319     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2320         board[i][holdingsColumn] = EmptySquare;
2321         board[i][countsColumn]   = (ChessSquare) 0;
2322     }
2323     while( (p=*holdings++) != NULLCHAR ) {
2324         piece = CharToPiece( ToUpper(p) );
2325         if(piece == EmptySquare) continue;
2326         /*j = (int) piece - (int) WhitePawn;*/
2327         j = PieceToNumber(piece);
2328         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2329         if(j < 0) continue;               /* should not happen */
2330         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2331         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2332         board[holdingsStartRow+j*direction][countsColumn]++;
2333     }
2334 }
2335
2336
2337 void
2338 VariantSwitch (Board board, VariantClass newVariant)
2339 {
2340    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2341    static Board oldBoard;
2342
2343    startedFromPositionFile = FALSE;
2344    if(gameInfo.variant == newVariant) return;
2345
2346    /* [HGM] This routine is called each time an assignment is made to
2347     * gameInfo.variant during a game, to make sure the board sizes
2348     * are set to match the new variant. If that means adding or deleting
2349     * holdings, we shift the playing board accordingly
2350     * This kludge is needed because in ICS observe mode, we get boards
2351     * of an ongoing game without knowing the variant, and learn about the
2352     * latter only later. This can be because of the move list we requested,
2353     * in which case the game history is refilled from the beginning anyway,
2354     * but also when receiving holdings of a crazyhouse game. In the latter
2355     * case we want to add those holdings to the already received position.
2356     */
2357
2358
2359    if (appData.debugMode) {
2360      fprintf(debugFP, "Switch board from %s to %s\n",
2361              VariantName(gameInfo.variant), VariantName(newVariant));
2362      setbuf(debugFP, NULL);
2363    }
2364    shuffleOpenings = 0;       /* [HGM] shuffle */
2365    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2366    switch(newVariant)
2367      {
2368      case VariantShogi:
2369        newWidth = 9;  newHeight = 9;
2370        gameInfo.holdingsSize = 7;
2371      case VariantBughouse:
2372      case VariantCrazyhouse:
2373        newHoldingsWidth = 2; break;
2374      case VariantGreat:
2375        newWidth = 10;
2376      case VariantSuper:
2377        newHoldingsWidth = 2;
2378        gameInfo.holdingsSize = 8;
2379        break;
2380      case VariantGothic:
2381      case VariantCapablanca:
2382      case VariantCapaRandom:
2383        newWidth = 10;
2384      default:
2385        newHoldingsWidth = gameInfo.holdingsSize = 0;
2386      };
2387
2388    if(newWidth  != gameInfo.boardWidth  ||
2389       newHeight != gameInfo.boardHeight ||
2390       newHoldingsWidth != gameInfo.holdingsWidth ) {
2391
2392      /* shift position to new playing area, if needed */
2393      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2394        for(i=0; i<BOARD_HEIGHT; i++)
2395          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2396            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2397              board[i][j];
2398        for(i=0; i<newHeight; i++) {
2399          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2400          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2401        }
2402      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2403        for(i=0; i<BOARD_HEIGHT; i++)
2404          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2405            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2406              board[i][j];
2407      }
2408      gameInfo.boardWidth  = newWidth;
2409      gameInfo.boardHeight = newHeight;
2410      gameInfo.holdingsWidth = newHoldingsWidth;
2411      gameInfo.variant = newVariant;
2412      InitDrawingSizes(-2, 0);
2413    } else gameInfo.variant = newVariant;
2414    CopyBoard(oldBoard, board);   // remember correctly formatted board
2415      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2416    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2417 }
2418
2419 static int loggedOn = FALSE;
2420
2421 /*-- Game start info cache: --*/
2422 int gs_gamenum;
2423 char gs_kind[MSG_SIZ];
2424 static char player1Name[128] = "";
2425 static char player2Name[128] = "";
2426 static char cont_seq[] = "\n\\   ";
2427 static int player1Rating = -1;
2428 static int player2Rating = -1;
2429 /*----------------------------*/
2430
2431 ColorClass curColor = ColorNormal;
2432 int suppressKibitz = 0;
2433
2434 // [HGM] seekgraph
2435 Boolean soughtPending = FALSE;
2436 Boolean seekGraphUp;
2437 #define MAX_SEEK_ADS 200
2438 #define SQUARE 0x80
2439 char *seekAdList[MAX_SEEK_ADS];
2440 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2441 float tcList[MAX_SEEK_ADS];
2442 char colorList[MAX_SEEK_ADS];
2443 int nrOfSeekAds = 0;
2444 int minRating = 1010, maxRating = 2800;
2445 int hMargin = 10, vMargin = 20, h, w;
2446 extern int squareSize, lineGap;
2447
2448 void
2449 PlotSeekAd (int i)
2450 {
2451         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2452         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2453         if(r < minRating+100 && r >=0 ) r = minRating+100;
2454         if(r > maxRating) r = maxRating;
2455         if(tc < 1.) tc = 1.;
2456         if(tc > 95.) tc = 95.;
2457         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2458         y = ((double)r - minRating)/(maxRating - minRating)
2459             * (h-vMargin-squareSize/8-1) + vMargin;
2460         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2461         if(strstr(seekAdList[i], " u ")) color = 1;
2462         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2463            !strstr(seekAdList[i], "bullet") &&
2464            !strstr(seekAdList[i], "blitz") &&
2465            !strstr(seekAdList[i], "standard") ) color = 2;
2466         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2467         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2468 }
2469
2470 void
2471 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2472 {
2473         char buf[MSG_SIZ], *ext = "";
2474         VariantClass v = StringToVariant(type);
2475         if(strstr(type, "wild")) {
2476             ext = type + 4; // append wild number
2477             if(v == VariantFischeRandom) type = "chess960"; else
2478             if(v == VariantLoadable) type = "setup"; else
2479             type = VariantName(v);
2480         }
2481         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2482         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2483             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2484             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2485             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2486             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2487             seekNrList[nrOfSeekAds] = nr;
2488             zList[nrOfSeekAds] = 0;
2489             seekAdList[nrOfSeekAds++] = StrSave(buf);
2490             if(plot) PlotSeekAd(nrOfSeekAds-1);
2491         }
2492 }
2493
2494 void
2495 EraseSeekDot (int i)
2496 {
2497     int x = xList[i], y = yList[i], d=squareSize/4, k;
2498     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2499     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2500     // now replot every dot that overlapped
2501     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2502         int xx = xList[k], yy = yList[k];
2503         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2504             DrawSeekDot(xx, yy, colorList[k]);
2505     }
2506 }
2507
2508 void
2509 RemoveSeekAd (int nr)
2510 {
2511         int i;
2512         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2513             EraseSeekDot(i);
2514             if(seekAdList[i]) free(seekAdList[i]);
2515             seekAdList[i] = seekAdList[--nrOfSeekAds];
2516             seekNrList[i] = seekNrList[nrOfSeekAds];
2517             ratingList[i] = ratingList[nrOfSeekAds];
2518             colorList[i]  = colorList[nrOfSeekAds];
2519             tcList[i] = tcList[nrOfSeekAds];
2520             xList[i]  = xList[nrOfSeekAds];
2521             yList[i]  = yList[nrOfSeekAds];
2522             zList[i]  = zList[nrOfSeekAds];
2523             seekAdList[nrOfSeekAds] = NULL;
2524             break;
2525         }
2526 }
2527
2528 Boolean
2529 MatchSoughtLine (char *line)
2530 {
2531     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2532     int nr, base, inc, u=0; char dummy;
2533
2534     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2535        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2536        (u=1) &&
2537        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2538         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2539         // match: compact and save the line
2540         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2541         return TRUE;
2542     }
2543     return FALSE;
2544 }
2545
2546 int
2547 DrawSeekGraph ()
2548 {
2549     int i;
2550     if(!seekGraphUp) return FALSE;
2551     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2552     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2553
2554     DrawSeekBackground(0, 0, w, h);
2555     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2556     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2557     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2558         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2559         yy = h-1-yy;
2560         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2561         if(i%500 == 0) {
2562             char buf[MSG_SIZ];
2563             snprintf(buf, MSG_SIZ, "%d", i);
2564             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2565         }
2566     }
2567     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2568     for(i=1; i<100; i+=(i<10?1:5)) {
2569         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2570         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2571         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2572             char buf[MSG_SIZ];
2573             snprintf(buf, MSG_SIZ, "%d", i);
2574             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2575         }
2576     }
2577     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2578     return TRUE;
2579 }
2580
2581 int
2582 SeekGraphClick (ClickType click, int x, int y, int moving)
2583 {
2584     static int lastDown = 0, displayed = 0, lastSecond;
2585     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2586         if(click == Release || moving) return FALSE;
2587         nrOfSeekAds = 0;
2588         soughtPending = TRUE;
2589         SendToICS(ics_prefix);
2590         SendToICS("sought\n"); // should this be "sought all"?
2591     } else { // issue challenge based on clicked ad
2592         int dist = 10000; int i, closest = 0, second = 0;
2593         for(i=0; i<nrOfSeekAds; i++) {
2594             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2595             if(d < dist) { dist = d; closest = i; }
2596             second += (d - zList[i] < 120); // count in-range ads
2597             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2598         }
2599         if(dist < 120) {
2600             char buf[MSG_SIZ];
2601             second = (second > 1);
2602             if(displayed != closest || second != lastSecond) {
2603                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2604                 lastSecond = second; displayed = closest;
2605             }
2606             if(click == Press) {
2607                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2608                 lastDown = closest;
2609                 return TRUE;
2610             } // on press 'hit', only show info
2611             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2612             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2613             SendToICS(ics_prefix);
2614             SendToICS(buf);
2615             return TRUE; // let incoming board of started game pop down the graph
2616         } else if(click == Release) { // release 'miss' is ignored
2617             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2618             if(moving == 2) { // right up-click
2619                 nrOfSeekAds = 0; // refresh graph
2620                 soughtPending = TRUE;
2621                 SendToICS(ics_prefix);
2622                 SendToICS("sought\n"); // should this be "sought all"?
2623             }
2624             return TRUE;
2625         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2626         // press miss or release hit 'pop down' seek graph
2627         seekGraphUp = FALSE;
2628         DrawPosition(TRUE, NULL);
2629     }
2630     return TRUE;
2631 }
2632
2633 void
2634 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2635 {
2636 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2637 #define STARTED_NONE 0
2638 #define STARTED_MOVES 1
2639 #define STARTED_BOARD 2
2640 #define STARTED_OBSERVE 3
2641 #define STARTED_HOLDINGS 4
2642 #define STARTED_CHATTER 5
2643 #define STARTED_COMMENT 6
2644 #define STARTED_MOVES_NOHIDE 7
2645
2646     static int started = STARTED_NONE;
2647     static char parse[20000];
2648     static int parse_pos = 0;
2649     static char buf[BUF_SIZE + 1];
2650     static int firstTime = TRUE, intfSet = FALSE;
2651     static ColorClass prevColor = ColorNormal;
2652     static int savingComment = FALSE;
2653     static int cmatch = 0; // continuation sequence match
2654     char *bp;
2655     char str[MSG_SIZ];
2656     int i, oldi;
2657     int buf_len;
2658     int next_out;
2659     int tkind;
2660     int backup;    /* [DM] For zippy color lines */
2661     char *p;
2662     char talker[MSG_SIZ]; // [HGM] chat
2663     int channel;
2664
2665     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2666
2667     if (appData.debugMode) {
2668       if (!error) {
2669         fprintf(debugFP, "<ICS: ");
2670         show_bytes(debugFP, data, count);
2671         fprintf(debugFP, "\n");
2672       }
2673     }
2674
2675     if (appData.debugMode) { int f = forwardMostMove;
2676         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2677                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2678                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2679     }
2680     if (count > 0) {
2681         /* If last read ended with a partial line that we couldn't parse,
2682            prepend it to the new read and try again. */
2683         if (leftover_len > 0) {
2684             for (i=0; i<leftover_len; i++)
2685               buf[i] = buf[leftover_start + i];
2686         }
2687
2688     /* copy new characters into the buffer */
2689     bp = buf + leftover_len;
2690     buf_len=leftover_len;
2691     for (i=0; i<count; i++)
2692     {
2693         // ignore these
2694         if (data[i] == '\r')
2695             continue;
2696
2697         // join lines split by ICS?
2698         if (!appData.noJoin)
2699         {
2700             /*
2701                 Joining just consists of finding matches against the
2702                 continuation sequence, and discarding that sequence
2703                 if found instead of copying it.  So, until a match
2704                 fails, there's nothing to do since it might be the
2705                 complete sequence, and thus, something we don't want
2706                 copied.
2707             */
2708             if (data[i] == cont_seq[cmatch])
2709             {
2710                 cmatch++;
2711                 if (cmatch == strlen(cont_seq))
2712                 {
2713                     cmatch = 0; // complete match.  just reset the counter
2714
2715                     /*
2716                         it's possible for the ICS to not include the space
2717                         at the end of the last word, making our [correct]
2718                         join operation fuse two separate words.  the server
2719                         does this when the space occurs at the width setting.
2720                     */
2721                     if (!buf_len || buf[buf_len-1] != ' ')
2722                     {
2723                         *bp++ = ' ';
2724                         buf_len++;
2725                     }
2726                 }
2727                 continue;
2728             }
2729             else if (cmatch)
2730             {
2731                 /*
2732                     match failed, so we have to copy what matched before
2733                     falling through and copying this character.  In reality,
2734                     this will only ever be just the newline character, but
2735                     it doesn't hurt to be precise.
2736                 */
2737                 strncpy(bp, cont_seq, cmatch);
2738                 bp += cmatch;
2739                 buf_len += cmatch;
2740                 cmatch = 0;
2741             }
2742         }
2743
2744         // copy this char
2745         *bp++ = data[i];
2746         buf_len++;
2747     }
2748
2749         buf[buf_len] = NULLCHAR;
2750 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2751         next_out = 0;
2752         leftover_start = 0;
2753
2754         i = 0;
2755         while (i < buf_len) {
2756             /* Deal with part of the TELNET option negotiation
2757                protocol.  We refuse to do anything beyond the
2758                defaults, except that we allow the WILL ECHO option,
2759                which ICS uses to turn off password echoing when we are
2760                directly connected to it.  We reject this option
2761                if localLineEditing mode is on (always on in xboard)
2762                and we are talking to port 23, which might be a real
2763                telnet server that will try to keep WILL ECHO on permanently.
2764              */
2765             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2766                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2767                 unsigned char option;
2768                 oldi = i;
2769                 switch ((unsigned char) buf[++i]) {
2770                   case TN_WILL:
2771                     if (appData.debugMode)
2772                       fprintf(debugFP, "\n<WILL ");
2773                     switch (option = (unsigned char) buf[++i]) {
2774                       case TN_ECHO:
2775                         if (appData.debugMode)
2776                           fprintf(debugFP, "ECHO ");
2777                         /* Reply only if this is a change, according
2778                            to the protocol rules. */
2779                         if (remoteEchoOption) break;
2780                         if (appData.localLineEditing &&
2781                             atoi(appData.icsPort) == TN_PORT) {
2782                             TelnetRequest(TN_DONT, TN_ECHO);
2783                         } else {
2784                             EchoOff();
2785                             TelnetRequest(TN_DO, TN_ECHO);
2786                             remoteEchoOption = TRUE;
2787                         }
2788                         break;
2789                       default:
2790                         if (appData.debugMode)
2791                           fprintf(debugFP, "%d ", option);
2792                         /* Whatever this is, we don't want it. */
2793                         TelnetRequest(TN_DONT, option);
2794                         break;
2795                     }
2796                     break;
2797                   case TN_WONT:
2798                     if (appData.debugMode)
2799                       fprintf(debugFP, "\n<WONT ");
2800                     switch (option = (unsigned char) buf[++i]) {
2801                       case TN_ECHO:
2802                         if (appData.debugMode)
2803                           fprintf(debugFP, "ECHO ");
2804                         /* Reply only if this is a change, according
2805                            to the protocol rules. */
2806                         if (!remoteEchoOption) break;
2807                         EchoOn();
2808                         TelnetRequest(TN_DONT, TN_ECHO);
2809                         remoteEchoOption = FALSE;
2810                         break;
2811                       default:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "%d ", (unsigned char) option);
2814                         /* Whatever this is, it must already be turned
2815                            off, because we never agree to turn on
2816                            anything non-default, so according to the
2817                            protocol rules, we don't reply. */
2818                         break;
2819                     }
2820                     break;
2821                   case TN_DO:
2822                     if (appData.debugMode)
2823                       fprintf(debugFP, "\n<DO ");
2824                     switch (option = (unsigned char) buf[++i]) {
2825                       default:
2826                         /* Whatever this is, we refuse to do it. */
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", option);
2829                         TelnetRequest(TN_WONT, option);
2830                         break;
2831                     }
2832                     break;
2833                   case TN_DONT:
2834                     if (appData.debugMode)
2835                       fprintf(debugFP, "\n<DONT ");
2836                     switch (option = (unsigned char) buf[++i]) {
2837                       default:
2838                         if (appData.debugMode)
2839                           fprintf(debugFP, "%d ", option);
2840                         /* Whatever this is, we are already not doing
2841                            it, because we never agree to do anything
2842                            non-default, so according to the protocol
2843                            rules, we don't reply. */
2844                         break;
2845                     }
2846                     break;
2847                   case TN_IAC:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<IAC ");
2850                     /* Doubled IAC; pass it through */
2851                     i--;
2852                     break;
2853                   default:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2856                     /* Drop all other telnet commands on the floor */
2857                     break;
2858                 }
2859                 if (oldi > next_out)
2860                   SendToPlayer(&buf[next_out], oldi - next_out);
2861                 if (++i > next_out)
2862                   next_out = i;
2863                 continue;
2864             }
2865
2866             /* OK, this at least will *usually* work */
2867             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2868                 loggedOn = TRUE;
2869             }
2870
2871             if (loggedOn && !intfSet) {
2872                 if (ics_type == ICS_ICC) {
2873                   snprintf(str, MSG_SIZ,
2874                           "/set-quietly interface %s\n/set-quietly style 12\n",
2875                           programVersion);
2876                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2877                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2878                 } else if (ics_type == ICS_CHESSNET) {
2879                   snprintf(str, MSG_SIZ, "/style 12\n");
2880                 } else {
2881                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2882                   strcat(str, programVersion);
2883                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2884                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2885                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2886 #ifdef WIN32
2887                   strcat(str, "$iset nohighlight 1\n");
2888 #endif
2889                   strcat(str, "$iset lock 1\n$style 12\n");
2890                 }
2891                 SendToICS(str);
2892                 NotifyFrontendLogin();
2893                 intfSet = TRUE;
2894             }
2895
2896             if (started == STARTED_COMMENT) {
2897                 /* Accumulate characters in comment */
2898                 parse[parse_pos++] = buf[i];
2899                 if (buf[i] == '\n') {
2900                     parse[parse_pos] = NULLCHAR;
2901                     if(chattingPartner>=0) {
2902                         char mess[MSG_SIZ];
2903                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2904                         OutputChatMessage(chattingPartner, mess);
2905                         chattingPartner = -1;
2906                         next_out = i+1; // [HGM] suppress printing in ICS window
2907                     } else
2908                     if(!suppressKibitz) // [HGM] kibitz
2909                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2910                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2911                         int nrDigit = 0, nrAlph = 0, j;
2912                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2913                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2914                         parse[parse_pos] = NULLCHAR;
2915                         // try to be smart: if it does not look like search info, it should go to
2916                         // ICS interaction window after all, not to engine-output window.
2917                         for(j=0; j<parse_pos; j++) { // count letters and digits
2918                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2919                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2920                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2921                         }
2922                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2923                             int depth=0; float score;
2924                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2925                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2926                                 pvInfoList[forwardMostMove-1].depth = depth;
2927                                 pvInfoList[forwardMostMove-1].score = 100*score;
2928                             }
2929                             OutputKibitz(suppressKibitz, parse);
2930                         } else {
2931                             char tmp[MSG_SIZ];
2932                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2933                             SendToPlayer(tmp, strlen(tmp));
2934                         }
2935                         next_out = i+1; // [HGM] suppress printing in ICS window
2936                     }
2937                     started = STARTED_NONE;
2938                 } else {
2939                     /* Don't match patterns against characters in comment */
2940                     i++;
2941                     continue;
2942                 }
2943             }
2944             if (started == STARTED_CHATTER) {
2945                 if (buf[i] != '\n') {
2946                     /* Don't match patterns against characters in chatter */
2947                     i++;
2948                     continue;
2949                 }
2950                 started = STARTED_NONE;
2951                 if(suppressKibitz) next_out = i+1;
2952             }
2953
2954             /* Kludge to deal with rcmd protocol */
2955             if (firstTime && looking_at(buf, &i, "\001*")) {
2956                 DisplayFatalError(&buf[1], 0, 1);
2957                 continue;
2958             } else {
2959                 firstTime = FALSE;
2960             }
2961
2962             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2963                 ics_type = ICS_ICC;
2964                 ics_prefix = "/";
2965                 if (appData.debugMode)
2966                   fprintf(debugFP, "ics_type %d\n", ics_type);
2967                 continue;
2968             }
2969             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2970                 ics_type = ICS_FICS;
2971                 ics_prefix = "$";
2972                 if (appData.debugMode)
2973                   fprintf(debugFP, "ics_type %d\n", ics_type);
2974                 continue;
2975             }
2976             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2977                 ics_type = ICS_CHESSNET;
2978                 ics_prefix = "/";
2979                 if (appData.debugMode)
2980                   fprintf(debugFP, "ics_type %d\n", ics_type);
2981                 continue;
2982             }
2983
2984             if (!loggedOn &&
2985                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2986                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2987                  looking_at(buf, &i, "will be \"*\""))) {
2988               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2989               continue;
2990             }
2991
2992             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2993               char buf[MSG_SIZ];
2994               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2995               DisplayIcsInteractionTitle(buf);
2996               have_set_title = TRUE;
2997             }
2998
2999             /* skip finger notes */
3000             if (started == STARTED_NONE &&
3001                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3002                  (buf[i] == '1' && buf[i+1] == '0')) &&
3003                 buf[i+2] == ':' && buf[i+3] == ' ') {
3004               started = STARTED_CHATTER;
3005               i += 3;
3006               continue;
3007             }
3008
3009             oldi = i;
3010             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3011             if(appData.seekGraph) {
3012                 if(soughtPending && MatchSoughtLine(buf+i)) {
3013                     i = strstr(buf+i, "rated") - buf;
3014                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3015                     next_out = leftover_start = i;
3016                     started = STARTED_CHATTER;
3017                     suppressKibitz = TRUE;
3018                     continue;
3019                 }
3020                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3021                         && looking_at(buf, &i, "* ads displayed")) {
3022                     soughtPending = FALSE;
3023                     seekGraphUp = TRUE;
3024                     DrawSeekGraph();
3025                     continue;
3026                 }
3027                 if(appData.autoRefresh) {
3028                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3029                         int s = (ics_type == ICS_ICC); // ICC format differs
3030                         if(seekGraphUp)
3031                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3032                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3033                         looking_at(buf, &i, "*% "); // eat prompt
3034                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3035                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3036                         next_out = i; // suppress
3037                         continue;
3038                     }
3039                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3040                         char *p = star_match[0];
3041                         while(*p) {
3042                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3043                             while(*p && *p++ != ' '); // next
3044                         }
3045                         looking_at(buf, &i, "*% "); // eat prompt
3046                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3047                         next_out = i;
3048                         continue;
3049                     }
3050                 }
3051             }
3052
3053             /* skip formula vars */
3054             if (started == STARTED_NONE &&
3055                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3062             if (appData.autoKibitz && started == STARTED_NONE &&
3063                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3064                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3065                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3066                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3067                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3068                         suppressKibitz = TRUE;
3069                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3070                         next_out = i;
3071                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3072                                 && (gameMode == IcsPlayingWhite)) ||
3073                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3074                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3075                             started = STARTED_CHATTER; // own kibitz we simply discard
3076                         else {
3077                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3078                             parse_pos = 0; parse[0] = NULLCHAR;
3079                             savingComment = TRUE;
3080                             suppressKibitz = gameMode != IcsObserving ? 2 :
3081                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3082                         }
3083                         continue;
3084                 } else
3085                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3086                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3087                          && atoi(star_match[0])) {
3088                     // suppress the acknowledgements of our own autoKibitz
3089                     char *p;
3090                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3091                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3092                     SendToPlayer(star_match[0], strlen(star_match[0]));
3093                     if(looking_at(buf, &i, "*% ")) // eat prompt
3094                         suppressKibitz = FALSE;
3095                     next_out = i;
3096                     continue;
3097                 }
3098             } // [HGM] kibitz: end of patch
3099
3100             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3101
3102             // [HGM] chat: intercept tells by users for which we have an open chat window
3103             channel = -1;
3104             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3105                                            looking_at(buf, &i, "* whispers:") ||
3106                                            looking_at(buf, &i, "* kibitzes:") ||
3107                                            looking_at(buf, &i, "* shouts:") ||
3108                                            looking_at(buf, &i, "* c-shouts:") ||
3109                                            looking_at(buf, &i, "--> * ") ||
3110                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3111                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3112                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3113                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3114                 int p;
3115                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3116                 chattingPartner = -1;
3117
3118                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3119                 for(p=0; p<MAX_CHAT; p++) {
3120                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3121                     talker[0] = '['; strcat(talker, "] ");
3122                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3123                     chattingPartner = p; break;
3124                     }
3125                 } else
3126                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3127                 for(p=0; p<MAX_CHAT; p++) {
3128                     if(!strcmp("kibitzes", chatPartner[p])) {
3129                         talker[0] = '['; strcat(talker, "] ");
3130                         chattingPartner = p; break;
3131                     }
3132                 } else
3133                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3134                 for(p=0; p<MAX_CHAT; p++) {
3135                     if(!strcmp("whispers", chatPartner[p])) {
3136                         talker[0] = '['; strcat(talker, "] ");
3137                         chattingPartner = p; break;
3138                     }
3139                 } else
3140                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3141                   if(buf[i-8] == '-' && buf[i-3] == 't')
3142                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3143                     if(!strcmp("c-shouts", chatPartner[p])) {
3144                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3145                         chattingPartner = p; break;
3146                     }
3147                   }
3148                   if(chattingPartner < 0)
3149                   for(p=0; p<MAX_CHAT; p++) {
3150                     if(!strcmp("shouts", chatPartner[p])) {
3151                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3152                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3153                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3154                         chattingPartner = p; break;
3155                     }
3156                   }
3157                 }
3158                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3159                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3160                     talker[0] = 0; Colorize(ColorTell, FALSE);
3161                     chattingPartner = p; break;
3162                 }
3163                 if(chattingPartner<0) i = oldi; else {
3164                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3165                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3166                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                     started = STARTED_COMMENT;
3168                     parse_pos = 0; parse[0] = NULLCHAR;
3169                     savingComment = 3 + chattingPartner; // counts as TRUE
3170                     suppressKibitz = TRUE;
3171                     continue;
3172                 }
3173             } // [HGM] chat: end of patch
3174
3175           backup = i;
3176             if (appData.zippyTalk || appData.zippyPlay) {
3177                 /* [DM] Backup address for color zippy lines */
3178 #if ZIPPY
3179                if (loggedOn == TRUE)
3180                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3181                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3182 #endif
3183             } // [DM] 'else { ' deleted
3184                 if (
3185                     /* Regular tells and says */
3186                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3187                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3188                     looking_at(buf, &i, "* says: ") ||
3189                     /* Don't color "message" or "messages" output */
3190                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3191                     looking_at(buf, &i, "*. * at *:*: ") ||
3192                     looking_at(buf, &i, "--* (*:*): ") ||
3193                     /* Message notifications (same color as tells) */
3194                     looking_at(buf, &i, "* has left a message ") ||
3195                     looking_at(buf, &i, "* just sent you a message:\n") ||
3196                     /* Whispers and kibitzes */
3197                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3198                     looking_at(buf, &i, "* kibitzes: ") ||
3199                     /* Channel tells */
3200                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3201
3202                   if (tkind == 1 && strchr(star_match[0], ':')) {
3203                       /* Avoid "tells you:" spoofs in channels */
3204                      tkind = 3;
3205                   }
3206                   if (star_match[0][0] == NULLCHAR ||
3207                       strchr(star_match[0], ' ') ||
3208                       (tkind == 3 && strchr(star_match[1], ' '))) {
3209                     /* Reject bogus matches */
3210                     i = oldi;
3211                   } else {
3212                     if (appData.colorize) {
3213                       if (oldi > next_out) {
3214                         SendToPlayer(&buf[next_out], oldi - next_out);
3215                         next_out = oldi;
3216                       }
3217                       switch (tkind) {
3218                       case 1:
3219                         Colorize(ColorTell, FALSE);
3220                         curColor = ColorTell;
3221                         break;
3222                       case 2:
3223                         Colorize(ColorKibitz, FALSE);
3224                         curColor = ColorKibitz;
3225                         break;
3226                       case 3:
3227                         p = strrchr(star_match[1], '(');
3228                         if (p == NULL) {
3229                           p = star_match[1];
3230                         } else {
3231                           p++;
3232                         }
3233                         if (atoi(p) == 1) {
3234                           Colorize(ColorChannel1, FALSE);
3235                           curColor = ColorChannel1;
3236                         } else {
3237                           Colorize(ColorChannel, FALSE);
3238                           curColor = ColorChannel;
3239                         }
3240                         break;
3241                       case 5:
3242                         curColor = ColorNormal;
3243                         break;
3244                       }
3245                     }
3246                     if (started == STARTED_NONE && appData.autoComment &&
3247                         (gameMode == IcsObserving ||
3248                          gameMode == IcsPlayingWhite ||
3249                          gameMode == IcsPlayingBlack)) {
3250                       parse_pos = i - oldi;
3251                       memcpy(parse, &buf[oldi], parse_pos);
3252                       parse[parse_pos] = NULLCHAR;
3253                       started = STARTED_COMMENT;
3254                       savingComment = TRUE;
3255                     } else {
3256                       started = STARTED_CHATTER;
3257                       savingComment = FALSE;
3258                     }
3259                     loggedOn = TRUE;
3260                     continue;
3261                   }
3262                 }
3263
3264                 if (looking_at(buf, &i, "* s-shouts: ") ||
3265                     looking_at(buf, &i, "* c-shouts: ")) {
3266                     if (appData.colorize) {
3267                         if (oldi > next_out) {
3268                             SendToPlayer(&buf[next_out], oldi - next_out);
3269                             next_out = oldi;
3270                         }
3271                         Colorize(ColorSShout, FALSE);
3272                         curColor = ColorSShout;
3273                     }
3274                     loggedOn = TRUE;
3275                     started = STARTED_CHATTER;
3276                     continue;
3277                 }
3278
3279                 if (looking_at(buf, &i, "--->")) {
3280                     loggedOn = TRUE;
3281                     continue;
3282                 }
3283
3284                 if (looking_at(buf, &i, "* shouts: ") ||
3285                     looking_at(buf, &i, "--> ")) {
3286                     if (appData.colorize) {
3287                         if (oldi > next_out) {
3288                             SendToPlayer(&buf[next_out], oldi - next_out);
3289                             next_out = oldi;
3290                         }
3291                         Colorize(ColorShout, FALSE);
3292                         curColor = ColorShout;
3293                     }
3294                     loggedOn = TRUE;
3295                     started = STARTED_CHATTER;
3296                     continue;
3297                 }
3298
3299                 if (looking_at( buf, &i, "Challenge:")) {
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorChallenge, FALSE);
3306                         curColor = ColorChallenge;
3307                     }
3308                     loggedOn = TRUE;
3309                     continue;
3310                 }
3311
3312                 if (looking_at(buf, &i, "* offers you") ||
3313                     looking_at(buf, &i, "* offers to be") ||
3314                     looking_at(buf, &i, "* would like to") ||
3315                     looking_at(buf, &i, "* requests to") ||
3316                     looking_at(buf, &i, "Your opponent offers") ||
3317                     looking_at(buf, &i, "Your opponent requests")) {
3318
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorRequest, FALSE);
3325                         curColor = ColorRequest;
3326                     }
3327                     continue;
3328                 }
3329
3330                 if (looking_at(buf, &i, "* (*) seeking")) {
3331                     if (appData.colorize) {
3332                         if (oldi > next_out) {
3333                             SendToPlayer(&buf[next_out], oldi - next_out);
3334                             next_out = oldi;
3335                         }
3336                         Colorize(ColorSeek, FALSE);
3337                         curColor = ColorSeek;
3338                     }
3339                     continue;
3340             }
3341
3342           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3343
3344             if (looking_at(buf, &i, "\\   ")) {
3345                 if (prevColor != ColorNormal) {
3346                     if (oldi > next_out) {
3347                         SendToPlayer(&buf[next_out], oldi - next_out);
3348                         next_out = oldi;
3349                     }
3350                     Colorize(prevColor, TRUE);
3351                     curColor = prevColor;
3352                 }
3353                 if (savingComment) {
3354                     parse_pos = i - oldi;
3355                     memcpy(parse, &buf[oldi], parse_pos);
3356                     parse[parse_pos] = NULLCHAR;
3357                     started = STARTED_COMMENT;
3358                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3359                         chattingPartner = savingComment - 3; // kludge to remember the box
3360                 } else {
3361                     started = STARTED_CHATTER;
3362                 }
3363                 continue;
3364             }
3365
3366             if (looking_at(buf, &i, "Black Strength :") ||
3367                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3368                 looking_at(buf, &i, "<10>") ||
3369                 looking_at(buf, &i, "#@#")) {
3370                 /* Wrong board style */
3371                 loggedOn = TRUE;
3372                 SendToICS(ics_prefix);
3373                 SendToICS("set style 12\n");
3374                 SendToICS(ics_prefix);
3375                 SendToICS("refresh\n");
3376                 continue;
3377             }
3378
3379             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3380                 ICSInitScript();
3381                 have_sent_ICS_logon = 1;
3382                 continue;
3383             }
3384
3385             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3386                 (looking_at(buf, &i, "\n<12> ") ||
3387                  looking_at(buf, &i, "<12> "))) {
3388                 loggedOn = TRUE;
3389                 if (oldi > next_out) {
3390                     SendToPlayer(&buf[next_out], oldi - next_out);
3391                 }
3392                 next_out = i;
3393                 started = STARTED_BOARD;
3394                 parse_pos = 0;
3395                 continue;
3396             }
3397
3398             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3399                 looking_at(buf, &i, "<b1> ")) {
3400                 if (oldi > next_out) {
3401                     SendToPlayer(&buf[next_out], oldi - next_out);
3402                 }
3403                 next_out = i;
3404                 started = STARTED_HOLDINGS;
3405                 parse_pos = 0;
3406                 continue;
3407             }
3408
3409             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3410                 loggedOn = TRUE;
3411                 /* Header for a move list -- first line */
3412
3413                 switch (ics_getting_history) {
3414                   case H_FALSE:
3415                     switch (gameMode) {
3416                       case IcsIdle:
3417                       case BeginningOfGame:
3418                         /* User typed "moves" or "oldmoves" while we
3419                            were idle.  Pretend we asked for these
3420                            moves and soak them up so user can step
3421                            through them and/or save them.
3422                            */
3423                         Reset(FALSE, TRUE);
3424                         gameMode = IcsObserving;
3425                         ModeHighlight();
3426                         ics_gamenum = -1;
3427                         ics_getting_history = H_GOT_UNREQ_HEADER;
3428                         break;
3429                       case EditGame: /*?*/
3430                       case EditPosition: /*?*/
3431                         /* Should above feature work in these modes too? */
3432                         /* For now it doesn't */
3433                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3434                         break;
3435                       default:
3436                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3437                         break;
3438                     }
3439                     break;
3440                   case H_REQUESTED:
3441                     /* Is this the right one? */
3442                     if (gameInfo.white && gameInfo.black &&
3443                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3444                         strcmp(gameInfo.black, star_match[2]) == 0) {
3445                         /* All is well */
3446                         ics_getting_history = H_GOT_REQ_HEADER;
3447                     }
3448                     break;
3449                   case H_GOT_REQ_HEADER:
3450                   case H_GOT_UNREQ_HEADER:
3451                   case H_GOT_UNWANTED_HEADER:
3452                   case H_GETTING_MOVES:
3453                     /* Should not happen */
3454                     DisplayError(_("Error gathering move list: two headers"), 0);
3455                     ics_getting_history = H_FALSE;
3456                     break;
3457                 }
3458
3459                 /* Save player ratings into gameInfo if needed */
3460                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3461                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3462                     (gameInfo.whiteRating == -1 ||
3463                      gameInfo.blackRating == -1)) {
3464
3465                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3466                     gameInfo.blackRating = string_to_rating(star_match[3]);
3467                     if (appData.debugMode)
3468                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3469                               gameInfo.whiteRating, gameInfo.blackRating);
3470                 }
3471                 continue;
3472             }
3473
3474             if (looking_at(buf, &i,
3475               "* * match, initial time: * minute*, increment: * second")) {
3476                 /* Header for a move list -- second line */
3477                 /* Initial board will follow if this is a wild game */
3478                 if (gameInfo.event != NULL) free(gameInfo.event);
3479                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3480                 gameInfo.event = StrSave(str);
3481                 /* [HGM] we switched variant. Translate boards if needed. */
3482                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3483                 continue;
3484             }
3485
3486             if (looking_at(buf, &i, "Move  ")) {
3487                 /* Beginning of a move list */
3488                 switch (ics_getting_history) {
3489                   case H_FALSE:
3490                     /* Normally should not happen */
3491                     /* Maybe user hit reset while we were parsing */
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Happens if we are ignoring a move list that is not
3495                      * the one we just requested.  Common if the user
3496                      * tries to observe two games without turning off
3497                      * getMoveList */
3498                     break;
3499                   case H_GETTING_MOVES:
3500                     /* Should not happen */
3501                     DisplayError(_("Error gathering move list: nested"), 0);
3502                     ics_getting_history = H_FALSE;
3503                     break;
3504                   case H_GOT_REQ_HEADER:
3505                     ics_getting_history = H_GETTING_MOVES;
3506                     started = STARTED_MOVES;
3507                     parse_pos = 0;
3508                     if (oldi > next_out) {
3509                         SendToPlayer(&buf[next_out], oldi - next_out);
3510                     }
3511                     break;
3512                   case H_GOT_UNREQ_HEADER:
3513                     ics_getting_history = H_GETTING_MOVES;
3514                     started = STARTED_MOVES_NOHIDE;
3515                     parse_pos = 0;
3516                     break;
3517                   case H_GOT_UNWANTED_HEADER:
3518                     ics_getting_history = H_FALSE;
3519                     break;
3520                 }
3521                 continue;
3522             }
3523
3524             if (looking_at(buf, &i, "% ") ||
3525                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3526                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3527                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3528                     soughtPending = FALSE;
3529                     seekGraphUp = TRUE;
3530                     DrawSeekGraph();
3531                 }
3532                 if(suppressKibitz) next_out = i;
3533                 savingComment = FALSE;
3534                 suppressKibitz = 0;
3535                 switch (started) {
3536                   case STARTED_MOVES:
3537                   case STARTED_MOVES_NOHIDE:
3538                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3539                     parse[parse_pos + i - oldi] = NULLCHAR;
3540                     ParseGameHistory(parse);
3541 #if ZIPPY
3542                     if (appData.zippyPlay && first.initDone) {
3543                         FeedMovesToProgram(&first, forwardMostMove);
3544                         if (gameMode == IcsPlayingWhite) {
3545                             if (WhiteOnMove(forwardMostMove)) {
3546                                 if (first.sendTime) {
3547                                   if (first.useColors) {
3548                                     SendToProgram("black\n", &first);
3549                                   }
3550                                   SendTimeRemaining(&first, TRUE);
3551                                 }
3552                                 if (first.useColors) {
3553                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3554                                 }
3555                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3556                                 first.maybeThinking = TRUE;
3557                             } else {
3558                                 if (first.usePlayother) {
3559                                   if (first.sendTime) {
3560                                     SendTimeRemaining(&first, TRUE);
3561                                   }
3562                                   SendToProgram("playother\n", &first);
3563                                   firstMove = FALSE;
3564                                 } else {
3565                                   firstMove = TRUE;
3566                                 }
3567                             }
3568                         } else if (gameMode == IcsPlayingBlack) {
3569                             if (!WhiteOnMove(forwardMostMove)) {
3570                                 if (first.sendTime) {
3571                                   if (first.useColors) {
3572                                     SendToProgram("white\n", &first);
3573                                   }
3574                                   SendTimeRemaining(&first, FALSE);
3575                                 }
3576                                 if (first.useColors) {
3577                                   SendToProgram("black\n", &first);
3578                                 }
3579                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3580                                 first.maybeThinking = TRUE;
3581                             } else {
3582                                 if (first.usePlayother) {
3583                                   if (first.sendTime) {
3584                                     SendTimeRemaining(&first, FALSE);
3585                                   }
3586                                   SendToProgram("playother\n", &first);
3587                                   firstMove = FALSE;
3588                                 } else {
3589                                   firstMove = TRUE;
3590                                 }
3591                             }
3592                         }
3593                     }
3594 #endif
3595                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3596                         /* Moves came from oldmoves or moves command
3597                            while we weren't doing anything else.
3598                            */
3599                         currentMove = forwardMostMove;
3600                         ClearHighlights();/*!!could figure this out*/
3601                         flipView = appData.flipView;
3602                         DrawPosition(TRUE, boards[currentMove]);
3603                         DisplayBothClocks();
3604                         snprintf(str, MSG_SIZ, "%s %s %s",
3605                                 gameInfo.white, _("vs."),  gameInfo.black);
3606                         DisplayTitle(str);
3607                         gameMode = IcsIdle;
3608                     } else {
3609                         /* Moves were history of an active game */
3610                         if (gameInfo.resultDetails != NULL) {
3611                             free(gameInfo.resultDetails);
3612                             gameInfo.resultDetails = NULL;
3613                         }
3614                     }
3615                     HistorySet(parseList, backwardMostMove,
3616                                forwardMostMove, currentMove-1);
3617                     DisplayMove(currentMove - 1);
3618                     if (started == STARTED_MOVES) next_out = i;
3619                     started = STARTED_NONE;
3620                     ics_getting_history = H_FALSE;
3621                     break;
3622
3623                   case STARTED_OBSERVE:
3624                     started = STARTED_NONE;
3625                     SendToICS(ics_prefix);
3626                     SendToICS("refresh\n");
3627                     break;
3628
3629                   default:
3630                     break;
3631                 }
3632                 if(bookHit) { // [HGM] book: simulate book reply
3633                     static char bookMove[MSG_SIZ]; // a bit generous?
3634
3635                     programStats.nodes = programStats.depth = programStats.time =
3636                     programStats.score = programStats.got_only_move = 0;
3637                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3638
3639                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3640                     strcat(bookMove, bookHit);
3641                     HandleMachineMove(bookMove, &first);
3642                 }
3643                 continue;
3644             }
3645
3646             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3647                  started == STARTED_HOLDINGS ||
3648                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3649                 /* Accumulate characters in move list or board */
3650                 parse[parse_pos++] = buf[i];
3651             }
3652
3653             /* Start of game messages.  Mostly we detect start of game
3654                when the first board image arrives.  On some versions
3655                of the ICS, though, we need to do a "refresh" after starting
3656                to observe in order to get the current board right away. */
3657             if (looking_at(buf, &i, "Adding game * to observation list")) {
3658                 started = STARTED_OBSERVE;
3659                 continue;
3660             }
3661
3662             /* Handle auto-observe */
3663             if (appData.autoObserve &&
3664                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3665                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3666                 char *player;
3667                 /* Choose the player that was highlighted, if any. */
3668                 if (star_match[0][0] == '\033' ||
3669                     star_match[1][0] != '\033') {
3670                     player = star_match[0];
3671                 } else {
3672                     player = star_match[2];
3673                 }
3674                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3675                         ics_prefix, StripHighlightAndTitle(player));
3676                 SendToICS(str);
3677
3678                 /* Save ratings from notify string */
3679                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3680                 player1Rating = string_to_rating(star_match[1]);
3681                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3682                 player2Rating = string_to_rating(star_match[3]);
3683
3684                 if (appData.debugMode)
3685                   fprintf(debugFP,
3686                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3687                           player1Name, player1Rating,
3688                           player2Name, player2Rating);
3689
3690                 continue;
3691             }
3692
3693             /* Deal with automatic examine mode after a game,
3694                and with IcsObserving -> IcsExamining transition */
3695             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3696                 looking_at(buf, &i, "has made you an examiner of game *")) {
3697
3698                 int gamenum = atoi(star_match[0]);
3699                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3700                     gamenum == ics_gamenum) {
3701                     /* We were already playing or observing this game;
3702                        no need to refetch history */
3703                     gameMode = IcsExamining;
3704                     if (pausing) {
3705                         pauseExamForwardMostMove = forwardMostMove;
3706                     } else if (currentMove < forwardMostMove) {
3707                         ForwardInner(forwardMostMove);
3708                     }
3709                 } else {
3710                     /* I don't think this case really can happen */
3711                     SendToICS(ics_prefix);
3712                     SendToICS("refresh\n");
3713                 }
3714                 continue;
3715             }
3716
3717             /* Error messages */
3718 //          if (ics_user_moved) {
3719             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3720                 if (looking_at(buf, &i, "Illegal move") ||
3721                     looking_at(buf, &i, "Not a legal move") ||
3722                     looking_at(buf, &i, "Your king is in check") ||
3723                     looking_at(buf, &i, "It isn't your turn") ||
3724                     looking_at(buf, &i, "It is not your move")) {
3725                     /* Illegal move */
3726                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3727                         currentMove = forwardMostMove-1;
3728                         DisplayMove(currentMove - 1); /* before DMError */
3729                         DrawPosition(FALSE, boards[currentMove]);
3730                         SwitchClocks(forwardMostMove-1); // [HGM] race
3731                         DisplayBothClocks();
3732                     }
3733                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3734                     ics_user_moved = 0;
3735                     continue;
3736                 }
3737             }
3738
3739             if (looking_at(buf, &i, "still have time") ||
3740                 looking_at(buf, &i, "not out of time") ||
3741                 looking_at(buf, &i, "either player is out of time") ||
3742                 looking_at(buf, &i, "has timeseal; checking")) {
3743                 /* We must have called his flag a little too soon */
3744                 whiteFlag = blackFlag = FALSE;
3745                 continue;
3746             }
3747
3748             if (looking_at(buf, &i, "added * seconds to") ||
3749                 looking_at(buf, &i, "seconds were added to")) {
3750                 /* Update the clocks */
3751                 SendToICS(ics_prefix);
3752                 SendToICS("refresh\n");
3753                 continue;
3754             }
3755
3756             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3757                 ics_clock_paused = TRUE;
3758                 StopClocks();
3759                 continue;
3760             }
3761
3762             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3763                 ics_clock_paused = FALSE;
3764                 StartClocks();
3765                 continue;
3766             }
3767
3768             /* Grab player ratings from the Creating: message.
3769                Note we have to check for the special case when
3770                the ICS inserts things like [white] or [black]. */
3771             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3772                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3773                 /* star_matches:
3774                    0    player 1 name (not necessarily white)
3775                    1    player 1 rating
3776                    2    empty, white, or black (IGNORED)
3777                    3    player 2 name (not necessarily black)
3778                    4    player 2 rating
3779
3780                    The names/ratings are sorted out when the game
3781                    actually starts (below).
3782                 */
3783                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3784                 player1Rating = string_to_rating(star_match[1]);
3785                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3786                 player2Rating = string_to_rating(star_match[4]);
3787
3788                 if (appData.debugMode)
3789                   fprintf(debugFP,
3790                           "Ratings from 'Creating:' %s %d, %s %d\n",
3791                           player1Name, player1Rating,
3792                           player2Name, player2Rating);
3793
3794                 continue;
3795             }
3796
3797             /* Improved generic start/end-of-game messages */
3798             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3799                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3800                 /* If tkind == 0: */
3801                 /* star_match[0] is the game number */
3802                 /*           [1] is the white player's name */
3803                 /*           [2] is the black player's name */
3804                 /* For end-of-game: */
3805                 /*           [3] is the reason for the game end */
3806                 /*           [4] is a PGN end game-token, preceded by " " */
3807                 /* For start-of-game: */
3808                 /*           [3] begins with "Creating" or "Continuing" */
3809                 /*           [4] is " *" or empty (don't care). */
3810                 int gamenum = atoi(star_match[0]);
3811                 char *whitename, *blackname, *why, *endtoken;
3812                 ChessMove endtype = EndOfFile;
3813
3814                 if (tkind == 0) {
3815                   whitename = star_match[1];
3816                   blackname = star_match[2];
3817                   why = star_match[3];
3818                   endtoken = star_match[4];
3819                 } else {
3820                   whitename = star_match[1];
3821                   blackname = star_match[3];
3822                   why = star_match[5];
3823                   endtoken = star_match[6];
3824                 }
3825
3826                 /* Game start messages */
3827                 if (strncmp(why, "Creating ", 9) == 0 ||
3828                     strncmp(why, "Continuing ", 11) == 0) {
3829                     gs_gamenum = gamenum;
3830                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3831                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3832                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3833 #if ZIPPY
3834                     if (appData.zippyPlay) {
3835                         ZippyGameStart(whitename, blackname);
3836                     }
3837 #endif /*ZIPPY*/
3838                     partnerBoardValid = FALSE; // [HGM] bughouse
3839                     continue;
3840                 }
3841
3842                 /* Game end messages */
3843                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3844                     ics_gamenum != gamenum) {
3845                     continue;
3846                 }
3847                 while (endtoken[0] == ' ') endtoken++;
3848                 switch (endtoken[0]) {
3849                   case '*':
3850                   default:
3851                     endtype = GameUnfinished;
3852                     break;
3853                   case '0':
3854                     endtype = BlackWins;
3855                     break;
3856                   case '1':
3857                     if (endtoken[1] == '/')
3858                       endtype = GameIsDrawn;
3859                     else
3860                   &nb