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