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