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