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