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