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