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