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