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