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