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