Implement kludge to set options through Move Type-in
[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     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1246     do {
1247         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1248         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1249         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1250         if(movenr == -1) return time;    /* last move before new session     */
1251         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1252         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1253         if(!moves) return increment;     /* current session is incremental   */
1254         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1255     } while(movenr >= -1);               /* try again for next session       */
1256
1257     return 0; // no new time quota on this move
1258 }
1259
1260 int
1261 ParseTimeControl (char *tc, float ti, int mps)
1262 {
1263   long tc1;
1264   long tc2;
1265   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1266   int min, sec=0;
1267
1268   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1269   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1270       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1271   if(ti > 0) {
1272
1273     if(mps)
1274       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1275     else 
1276       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1277   } else {
1278     if(mps)
1279       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1280     else 
1281       snprintf(buf, MSG_SIZ, ":%s", mytc);
1282   }
1283   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1284   
1285   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1286     return FALSE;
1287   }
1288
1289   if( *tc == '/' ) {
1290     /* Parse second time control */
1291     tc++;
1292
1293     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1294       return FALSE;
1295     }
1296
1297     if( tc2 == 0 ) {
1298       return FALSE;
1299     }
1300
1301     timeControl_2 = tc2 * 1000;
1302   }
1303   else {
1304     timeControl_2 = 0;
1305   }
1306
1307   if( tc1 == 0 ) {
1308     return FALSE;
1309   }
1310
1311   timeControl = tc1 * 1000;
1312
1313   if (ti >= 0) {
1314     timeIncrement = ti * 1000;  /* convert to ms */
1315     movesPerSession = 0;
1316   } else {
1317     timeIncrement = 0;
1318     movesPerSession = mps;
1319   }
1320   return TRUE;
1321 }
1322
1323 void
1324 InitBackEnd2 ()
1325 {
1326     if (appData.debugMode) {
1327         fprintf(debugFP, "%s\n", programVersion);
1328     }
1329     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1330
1331     set_cont_sequence(appData.wrapContSeq);
1332     if (appData.matchGames > 0) {
1333         appData.matchMode = TRUE;
1334     } else if (appData.matchMode) {
1335         appData.matchGames = 1;
1336     }
1337     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1338         appData.matchGames = appData.sameColorGames;
1339     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1340         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1341         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1342     }
1343     Reset(TRUE, FALSE);
1344     if (appData.noChessProgram || first.protocolVersion == 1) {
1345       InitBackEnd3();
1346     } else {
1347       /* kludge: allow timeout for initial "feature" commands */
1348       FreezeUI();
1349       DisplayMessage("", _("Starting chess program"));
1350       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1351     }
1352 }
1353
1354 int
1355 CalculateIndex (int index, int gameNr)
1356 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1357     int res;
1358     if(index > 0) return index; // fixed nmber
1359     if(index == 0) return 1;
1360     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1361     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1362     return res;
1363 }
1364
1365 int
1366 LoadGameOrPosition (int gameNr)
1367 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1368     if (*appData.loadGameFile != NULLCHAR) {
1369         if (!LoadGameFromFile(appData.loadGameFile,
1370                 CalculateIndex(appData.loadGameIndex, gameNr),
1371                               appData.loadGameFile, FALSE)) {
1372             DisplayFatalError(_("Bad game file"), 0, 1);
1373             return 0;
1374         }
1375     } else if (*appData.loadPositionFile != NULLCHAR) {
1376         if (!LoadPositionFromFile(appData.loadPositionFile,
1377                 CalculateIndex(appData.loadPositionIndex, gameNr),
1378                                   appData.loadPositionFile)) {
1379             DisplayFatalError(_("Bad position file"), 0, 1);
1380             return 0;
1381         }
1382     }
1383     return 1;
1384 }
1385
1386 void
1387 ReserveGame (int gameNr, char resChar)
1388 {
1389     FILE *tf = fopen(appData.tourneyFile, "r+");
1390     char *p, *q, c, buf[MSG_SIZ];
1391     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1392     safeStrCpy(buf, lastMsg, MSG_SIZ);
1393     DisplayMessage(_("Pick new game"), "");
1394     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1395     ParseArgsFromFile(tf);
1396     p = q = appData.results;
1397     if(appData.debugMode) {
1398       char *r = appData.participants;
1399       fprintf(debugFP, "results = '%s'\n", p);
1400       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1401       fprintf(debugFP, "\n");
1402     }
1403     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1404     nextGame = q - p;
1405     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1406     safeStrCpy(q, p, strlen(p) + 2);
1407     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1408     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1409     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1410         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1411         q[nextGame] = '*';
1412     }
1413     fseek(tf, -(strlen(p)+4), SEEK_END);
1414     c = fgetc(tf);
1415     if(c != '"') // depending on DOS or Unix line endings we can be one off
1416          fseek(tf, -(strlen(p)+2), SEEK_END);
1417     else fseek(tf, -(strlen(p)+3), SEEK_END);
1418     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1419     DisplayMessage(buf, "");
1420     free(p); appData.results = q;
1421     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1422        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1423       int round = appData.defaultMatchGames * appData.tourneyType;
1424       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1425          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1426         UnloadEngine(&first);  // next game belongs to other pairing;
1427         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1428     }
1429     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr);
1430 }
1431
1432 void
1433 MatchEvent (int mode)
1434 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1435         int dummy;
1436         if(matchMode) { // already in match mode: switch it off
1437             abortMatch = TRUE;
1438             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1439             return;
1440         }
1441 //      if(gameMode != BeginningOfGame) {
1442 //          DisplayError(_("You can only start a match from the initial position."), 0);
1443 //          return;
1444 //      }
1445         abortMatch = FALSE;
1446         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1447         /* Set up machine vs. machine match */
1448         nextGame = 0;
1449         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1450         if(appData.tourneyFile[0]) {
1451             ReserveGame(-1, 0);
1452             if(nextGame > appData.matchGames) {
1453                 char buf[MSG_SIZ];
1454                 if(strchr(appData.results, '*') == NULL) {
1455                     FILE *f;
1456                     appData.tourneyCycles++;
1457                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1458                         fclose(f);
1459                         NextTourneyGame(-1, &dummy);
1460                         ReserveGame(-1, 0);
1461                         if(nextGame <= appData.matchGames) {
1462                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1463                             matchMode = mode;
1464                             ScheduleDelayedEvent(NextMatchGame, 10000);
1465                             return;
1466                         }
1467                     }
1468                 }
1469                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1470                 DisplayError(buf, 0);
1471                 appData.tourneyFile[0] = 0;
1472                 return;
1473             }
1474         } else
1475         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1476             DisplayFatalError(_("Can't have a match with no chess programs"),
1477                               0, 2);
1478             return;
1479         }
1480         matchMode = mode;
1481         matchGame = roundNr = 1;
1482         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1483         NextMatchGame();
1484 }
1485
1486 void
1487 InitBackEnd3 P((void))
1488 {
1489     GameMode initialMode;
1490     char buf[MSG_SIZ];
1491     int err, len;
1492
1493     InitChessProgram(&first, startedFromSetupPosition);
1494
1495     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1496         free(programVersion);
1497         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1498         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1499         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1500     }
1501
1502     if (appData.icsActive) {
1503 #ifdef WIN32
1504         /* [DM] Make a console window if needed [HGM] merged ifs */
1505         ConsoleCreate();
1506 #endif
1507         err = establish();
1508         if (err != 0)
1509           {
1510             if (*appData.icsCommPort != NULLCHAR)
1511               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1512                              appData.icsCommPort);
1513             else
1514               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1515                         appData.icsHost, appData.icsPort);
1516
1517             if( (len >= MSG_SIZ) && appData.debugMode )
1518               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1519
1520             DisplayFatalError(buf, err, 1);
1521             return;
1522         }
1523         SetICSMode();
1524         telnetISR =
1525           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1526         fromUserISR =
1527           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1528         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1529             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1530     } else if (appData.noChessProgram) {
1531         SetNCPMode();
1532     } else {
1533         SetGNUMode();
1534     }
1535
1536     if (*appData.cmailGameName != NULLCHAR) {
1537         SetCmailMode();
1538         OpenLoopback(&cmailPR);
1539         cmailISR =
1540           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1541     }
1542
1543     ThawUI();
1544     DisplayMessage("", "");
1545     if (StrCaseCmp(appData.initialMode, "") == 0) {
1546       initialMode = BeginningOfGame;
1547       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1548         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1549         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1550         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1551         ModeHighlight();
1552       }
1553     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1554       initialMode = TwoMachinesPlay;
1555     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1556       initialMode = AnalyzeFile;
1557     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1558       initialMode = AnalyzeMode;
1559     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1560       initialMode = MachinePlaysWhite;
1561     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1562       initialMode = MachinePlaysBlack;
1563     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1564       initialMode = EditGame;
1565     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1566       initialMode = EditPosition;
1567     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1568       initialMode = Training;
1569     } else {
1570       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1571       if( (len >= MSG_SIZ) && appData.debugMode )
1572         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1573
1574       DisplayFatalError(buf, 0, 2);
1575       return;
1576     }
1577
1578     if (appData.matchMode) {
1579         if(appData.tourneyFile[0]) { // start tourney from command line
1580             FILE *f;
1581             if(f = fopen(appData.tourneyFile, "r")) {
1582                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1583                 fclose(f);
1584                 appData.clockMode = TRUE;
1585                 SetGNUMode();
1586             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1587         }
1588         MatchEvent(TRUE);
1589     } else if (*appData.cmailGameName != NULLCHAR) {
1590         /* Set up cmail mode */
1591         ReloadCmailMsgEvent(TRUE);
1592     } else {
1593         /* Set up other modes */
1594         if (initialMode == AnalyzeFile) {
1595           if (*appData.loadGameFile == NULLCHAR) {
1596             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1597             return;
1598           }
1599         }
1600         if (*appData.loadGameFile != NULLCHAR) {
1601             (void) LoadGameFromFile(appData.loadGameFile,
1602                                     appData.loadGameIndex,
1603                                     appData.loadGameFile, TRUE);
1604         } else if (*appData.loadPositionFile != NULLCHAR) {
1605             (void) LoadPositionFromFile(appData.loadPositionFile,
1606                                         appData.loadPositionIndex,
1607                                         appData.loadPositionFile);
1608             /* [HGM] try to make self-starting even after FEN load */
1609             /* to allow automatic setup of fairy variants with wtm */
1610             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1611                 gameMode = BeginningOfGame;
1612                 setboardSpoiledMachineBlack = 1;
1613             }
1614             /* [HGM] loadPos: make that every new game uses the setup */
1615             /* from file as long as we do not switch variant          */
1616             if(!blackPlaysFirst) {
1617                 startedFromPositionFile = TRUE;
1618                 CopyBoard(filePosition, boards[0]);
1619             }
1620         }
1621         if (initialMode == AnalyzeMode) {
1622           if (appData.noChessProgram) {
1623             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1624             return;
1625           }
1626           if (appData.icsActive) {
1627             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1628             return;
1629           }
1630           AnalyzeModeEvent();
1631         } else if (initialMode == AnalyzeFile) {
1632           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1633           ShowThinkingEvent();
1634           AnalyzeFileEvent();
1635           AnalysisPeriodicEvent(1);
1636         } else if (initialMode == MachinePlaysWhite) {
1637           if (appData.noChessProgram) {
1638             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1639                               0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1644                               0, 2);
1645             return;
1646           }
1647           MachineWhiteEvent();
1648         } else if (initialMode == MachinePlaysBlack) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineBlackEvent();
1660         } else if (initialMode == TwoMachinesPlay) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           TwoMachinesEvent();
1672         } else if (initialMode == EditGame) {
1673           EditGameEvent();
1674         } else if (initialMode == EditPosition) {
1675           EditPositionEvent();
1676         } else if (initialMode == Training) {
1677           if (*appData.loadGameFile == NULLCHAR) {
1678             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1679             return;
1680           }
1681           TrainingEvent();
1682         }
1683     }
1684 }
1685
1686 void
1687 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1688 {
1689     DisplayBook(current+1);
1690
1691     MoveHistorySet( movelist, first, last, current, pvInfoList );
1692
1693     EvalGraphSet( first, last, current, pvInfoList );
1694
1695     MakeEngineOutputTitle();
1696 }
1697
1698 /*
1699  * Establish will establish a contact to a remote host.port.
1700  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1701  *  used to talk to the host.
1702  * Returns 0 if okay, error code if not.
1703  */
1704 int
1705 establish ()
1706 {
1707     char buf[MSG_SIZ];
1708
1709     if (*appData.icsCommPort != NULLCHAR) {
1710         /* Talk to the host through a serial comm port */
1711         return OpenCommPort(appData.icsCommPort, &icsPR);
1712
1713     } else if (*appData.gateway != NULLCHAR) {
1714         if (*appData.remoteShell == NULLCHAR) {
1715             /* Use the rcmd protocol to run telnet program on a gateway host */
1716             snprintf(buf, sizeof(buf), "%s %s %s",
1717                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1718             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1719
1720         } else {
1721             /* Use the rsh program to run telnet program on a gateway host */
1722             if (*appData.remoteUser == NULLCHAR) {
1723                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1724                         appData.gateway, appData.telnetProgram,
1725                         appData.icsHost, appData.icsPort);
1726             } else {
1727                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1728                         appData.remoteShell, appData.gateway,
1729                         appData.remoteUser, appData.telnetProgram,
1730                         appData.icsHost, appData.icsPort);
1731             }
1732             return StartChildProcess(buf, "", &icsPR);
1733
1734         }
1735     } else if (appData.useTelnet) {
1736         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1737
1738     } else {
1739         /* TCP socket interface differs somewhat between
1740            Unix and NT; handle details in the front end.
1741            */
1742         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1743     }
1744 }
1745
1746 void
1747 EscapeExpand (char *p, char *q)
1748 {       // [HGM] initstring: routine to shape up string arguments
1749         while(*p++ = *q++) if(p[-1] == '\\')
1750             switch(*q++) {
1751                 case 'n': p[-1] = '\n'; break;
1752                 case 'r': p[-1] = '\r'; break;
1753                 case 't': p[-1] = '\t'; break;
1754                 case '\\': p[-1] = '\\'; break;
1755                 case 0: *p = 0; return;
1756                 default: p[-1] = q[-1]; break;
1757             }
1758 }
1759
1760 void
1761 show_bytes (FILE *fp, char *buf, int count)
1762 {
1763     while (count--) {
1764         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1765             fprintf(fp, "\\%03o", *buf & 0xff);
1766         } else {
1767             putc(*buf, fp);
1768         }
1769         buf++;
1770     }
1771     fflush(fp);
1772 }
1773
1774 /* Returns an errno value */
1775 int
1776 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1777 {
1778     char buf[8192], *p, *q, *buflim;
1779     int left, newcount, outcount;
1780
1781     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1782         *appData.gateway != NULLCHAR) {
1783         if (appData.debugMode) {
1784             fprintf(debugFP, ">ICS: ");
1785             show_bytes(debugFP, message, count);
1786             fprintf(debugFP, "\n");
1787         }
1788         return OutputToProcess(pr, message, count, outError);
1789     }
1790
1791     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1792     p = message;
1793     q = buf;
1794     left = count;
1795     newcount = 0;
1796     while (left) {
1797         if (q >= buflim) {
1798             if (appData.debugMode) {
1799                 fprintf(debugFP, ">ICS: ");
1800                 show_bytes(debugFP, buf, newcount);
1801                 fprintf(debugFP, "\n");
1802             }
1803             outcount = OutputToProcess(pr, buf, newcount, outError);
1804             if (outcount < newcount) return -1; /* to be sure */
1805             q = buf;
1806             newcount = 0;
1807         }
1808         if (*p == '\n') {
1809             *q++ = '\r';
1810             newcount++;
1811         } else if (((unsigned char) *p) == TN_IAC) {
1812             *q++ = (char) TN_IAC;
1813             newcount ++;
1814         }
1815         *q++ = *p++;
1816         newcount++;
1817         left--;
1818     }
1819     if (appData.debugMode) {
1820         fprintf(debugFP, ">ICS: ");
1821         show_bytes(debugFP, buf, newcount);
1822         fprintf(debugFP, "\n");
1823     }
1824     outcount = OutputToProcess(pr, buf, newcount, outError);
1825     if (outcount < newcount) return -1; /* to be sure */
1826     return count;
1827 }
1828
1829 void
1830 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1831 {
1832     int outError, outCount;
1833     static int gotEof = 0;
1834
1835     /* Pass data read from player on to ICS */
1836     if (count > 0) {
1837         gotEof = 0;
1838         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1839         if (outCount < count) {
1840             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1841         }
1842     } else if (count < 0) {
1843         RemoveInputSource(isr);
1844         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1845     } else if (gotEof++ > 0) {
1846         RemoveInputSource(isr);
1847         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1848     }
1849 }
1850
1851 void
1852 KeepAlive ()
1853 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1854     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1855     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1856     SendToICS("date\n");
1857     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1858 }
1859
1860 /* added routine for printf style output to ics */
1861 void
1862 ics_printf (char *format, ...)
1863 {
1864     char buffer[MSG_SIZ];
1865     va_list args;
1866
1867     va_start(args, format);
1868     vsnprintf(buffer, sizeof(buffer), format, args);
1869     buffer[sizeof(buffer)-1] = '\0';
1870     SendToICS(buffer);
1871     va_end(args);
1872 }
1873
1874 void
1875 SendToICS (char *s)
1876 {
1877     int count, outCount, outError;
1878
1879     if (icsPR == NoProc) return;
1880
1881     count = strlen(s);
1882     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1883     if (outCount < count) {
1884         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1885     }
1886 }
1887
1888 /* This is used for sending logon scripts to the ICS. Sending
1889    without a delay causes problems when using timestamp on ICC
1890    (at least on my machine). */
1891 void
1892 SendToICSDelayed (char *s, long msdelay)
1893 {
1894     int count, outCount, outError;
1895
1896     if (icsPR == NoProc) return;
1897
1898     count = strlen(s);
1899     if (appData.debugMode) {
1900         fprintf(debugFP, ">ICS: ");
1901         show_bytes(debugFP, s, count);
1902         fprintf(debugFP, "\n");
1903     }
1904     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1905                                       msdelay);
1906     if (outCount < count) {
1907         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1908     }
1909 }
1910
1911
1912 /* Remove all highlighting escape sequences in s
1913    Also deletes any suffix starting with '('
1914    */
1915 char *
1916 StripHighlightAndTitle (char *s)
1917 {
1918     static char retbuf[MSG_SIZ];
1919     char *p = retbuf;
1920
1921     while (*s != NULLCHAR) {
1922         while (*s == '\033') {
1923             while (*s != NULLCHAR && !isalpha(*s)) s++;
1924             if (*s != NULLCHAR) s++;
1925         }
1926         while (*s != NULLCHAR && *s != '\033') {
1927             if (*s == '(' || *s == '[') {
1928                 *p = NULLCHAR;
1929                 return retbuf;
1930             }
1931             *p++ = *s++;
1932         }
1933     }
1934     *p = NULLCHAR;
1935     return retbuf;
1936 }
1937
1938 /* Remove all highlighting escape sequences in s */
1939 char *
1940 StripHighlight (char *s)
1941 {
1942     static char retbuf[MSG_SIZ];
1943     char *p = retbuf;
1944
1945     while (*s != NULLCHAR) {
1946         while (*s == '\033') {
1947             while (*s != NULLCHAR && !isalpha(*s)) s++;
1948             if (*s != NULLCHAR) s++;
1949         }
1950         while (*s != NULLCHAR && *s != '\033') {
1951             *p++ = *s++;
1952         }
1953     }
1954     *p = NULLCHAR;
1955     return retbuf;
1956 }
1957
1958 char *variantNames[] = VARIANT_NAMES;
1959 char *
1960 VariantName (VariantClass v)
1961 {
1962     return variantNames[v];
1963 }
1964
1965
1966 /* Identify a variant from the strings the chess servers use or the
1967    PGN Variant tag names we use. */
1968 VariantClass
1969 StringToVariant (char *e)
1970 {
1971     char *p;
1972     int wnum = -1;
1973     VariantClass v = VariantNormal;
1974     int i, found = FALSE;
1975     char buf[MSG_SIZ];
1976     int len;
1977
1978     if (!e) return v;
1979
1980     /* [HGM] skip over optional board-size prefixes */
1981     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1982         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1983         while( *e++ != '_');
1984     }
1985
1986     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1987         v = VariantNormal;
1988         found = TRUE;
1989     } else
1990     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1991       if (StrCaseStr(e, variantNames[i])) {
1992         v = (VariantClass) i;
1993         found = TRUE;
1994         break;
1995       }
1996     }
1997
1998     if (!found) {
1999       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2000           || StrCaseStr(e, "wild/fr")
2001           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2002         v = VariantFischeRandom;
2003       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2004                  (i = 1, p = StrCaseStr(e, "w"))) {
2005         p += i;
2006         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2007         if (isdigit(*p)) {
2008           wnum = atoi(p);
2009         } else {
2010           wnum = -1;
2011         }
2012         switch (wnum) {
2013         case 0: /* FICS only, actually */
2014         case 1:
2015           /* Castling legal even if K starts on d-file */
2016           v = VariantWildCastle;
2017           break;
2018         case 2:
2019         case 3:
2020         case 4:
2021           /* Castling illegal even if K & R happen to start in
2022              normal positions. */
2023           v = VariantNoCastle;
2024           break;
2025         case 5:
2026         case 7:
2027         case 8:
2028         case 10:
2029         case 11:
2030         case 12:
2031         case 13:
2032         case 14:
2033         case 15:
2034         case 18:
2035         case 19:
2036           /* Castling legal iff K & R start in normal positions */
2037           v = VariantNormal;
2038           break;
2039         case 6:
2040         case 20:
2041         case 21:
2042           /* Special wilds for position setup; unclear what to do here */
2043           v = VariantLoadable;
2044           break;
2045         case 9:
2046           /* Bizarre ICC game */
2047           v = VariantTwoKings;
2048           break;
2049         case 16:
2050           v = VariantKriegspiel;
2051           break;
2052         case 17:
2053           v = VariantLosers;
2054           break;
2055         case 22:
2056           v = VariantFischeRandom;
2057           break;
2058         case 23:
2059           v = VariantCrazyhouse;
2060           break;
2061         case 24:
2062           v = VariantBughouse;
2063           break;
2064         case 25:
2065           v = Variant3Check;
2066           break;
2067         case 26:
2068           /* Not quite the same as FICS suicide! */
2069           v = VariantGiveaway;
2070           break;
2071         case 27:
2072           v = VariantAtomic;
2073           break;
2074         case 28:
2075           v = VariantShatranj;
2076           break;
2077
2078         /* Temporary names for future ICC types.  The name *will* change in
2079            the next xboard/WinBoard release after ICC defines it. */
2080         case 29:
2081           v = Variant29;
2082           break;
2083         case 30:
2084           v = Variant30;
2085           break;
2086         case 31:
2087           v = Variant31;
2088           break;
2089         case 32:
2090           v = Variant32;
2091           break;
2092         case 33:
2093           v = Variant33;
2094           break;
2095         case 34:
2096           v = Variant34;
2097           break;
2098         case 35:
2099           v = Variant35;
2100           break;
2101         case 36:
2102           v = Variant36;
2103           break;
2104         case 37:
2105           v = VariantShogi;
2106           break;
2107         case 38:
2108           v = VariantXiangqi;
2109           break;
2110         case 39:
2111           v = VariantCourier;
2112           break;
2113         case 40:
2114           v = VariantGothic;
2115           break;
2116         case 41:
2117           v = VariantCapablanca;
2118           break;
2119         case 42:
2120           v = VariantKnightmate;
2121           break;
2122         case 43:
2123           v = VariantFairy;
2124           break;
2125         case 44:
2126           v = VariantCylinder;
2127           break;
2128         case 45:
2129           v = VariantFalcon;
2130           break;
2131         case 46:
2132           v = VariantCapaRandom;
2133           break;
2134         case 47:
2135           v = VariantBerolina;
2136           break;
2137         case 48:
2138           v = VariantJanus;
2139           break;
2140         case 49:
2141           v = VariantSuper;
2142           break;
2143         case 50:
2144           v = VariantGreat;
2145           break;
2146         case -1:
2147           /* Found "wild" or "w" in the string but no number;
2148              must assume it's normal chess. */
2149           v = VariantNormal;
2150           break;
2151         default:
2152           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2153           if( (len >= MSG_SIZ) && appData.debugMode )
2154             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2155
2156           DisplayError(buf, 0);
2157           v = VariantUnknown;
2158           break;
2159         }
2160       }
2161     }
2162     if (appData.debugMode) {
2163       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2164               e, wnum, VariantName(v));
2165     }
2166     return v;
2167 }
2168
2169 static int leftover_start = 0, leftover_len = 0;
2170 char star_match[STAR_MATCH_N][MSG_SIZ];
2171
2172 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2173    advance *index beyond it, and set leftover_start to the new value of
2174    *index; else return FALSE.  If pattern contains the character '*', it
2175    matches any sequence of characters not containing '\r', '\n', or the
2176    character following the '*' (if any), and the matched sequence(s) are
2177    copied into star_match.
2178    */
2179 int
2180 looking_at ( char *buf, int *index, char *pattern)
2181 {
2182     char *bufp = &buf[*index], *patternp = pattern;
2183     int star_count = 0;
2184     char *matchp = star_match[0];
2185
2186     for (;;) {
2187         if (*patternp == NULLCHAR) {
2188             *index = leftover_start = bufp - buf;
2189             *matchp = NULLCHAR;
2190             return TRUE;
2191         }
2192         if (*bufp == NULLCHAR) return FALSE;
2193         if (*patternp == '*') {
2194             if (*bufp == *(patternp + 1)) {
2195                 *matchp = NULLCHAR;
2196                 matchp = star_match[++star_count];
2197                 patternp += 2;
2198                 bufp++;
2199                 continue;
2200             } else if (*bufp == '\n' || *bufp == '\r') {
2201                 patternp++;
2202                 if (*patternp == NULLCHAR)
2203                   continue;
2204                 else
2205                   return FALSE;
2206             } else {
2207                 *matchp++ = *bufp++;
2208                 continue;
2209             }
2210         }
2211         if (*patternp != *bufp) return FALSE;
2212         patternp++;
2213         bufp++;
2214     }
2215 }
2216
2217 void
2218 SendToPlayer (char *data, int length)
2219 {
2220     int error, outCount;
2221     outCount = OutputToProcess(NoProc, data, length, &error);
2222     if (outCount < length) {
2223         DisplayFatalError(_("Error writing to display"), error, 1);
2224     }
2225 }
2226
2227 void
2228 PackHolding (char packed[], char *holding)
2229 {
2230     char *p = holding;
2231     char *q = packed;
2232     int runlength = 0;
2233     int curr = 9999;
2234     do {
2235         if (*p == curr) {
2236             runlength++;
2237         } else {
2238             switch (runlength) {
2239               case 0:
2240                 break;
2241               case 1:
2242                 *q++ = curr;
2243                 break;
2244               case 2:
2245                 *q++ = curr;
2246                 *q++ = curr;
2247                 break;
2248               default:
2249                 sprintf(q, "%d", runlength);
2250                 while (*q) q++;
2251                 *q++ = curr;
2252                 break;
2253             }
2254             runlength = 1;
2255             curr = *p;
2256         }
2257     } while (*p++);
2258     *q = NULLCHAR;
2259 }
2260
2261 /* Telnet protocol requests from the front end */
2262 void
2263 TelnetRequest (unsigned char ddww, unsigned char option)
2264 {
2265     unsigned char msg[3];
2266     int outCount, outError;
2267
2268     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2269
2270     if (appData.debugMode) {
2271         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2272         switch (ddww) {
2273           case TN_DO:
2274             ddwwStr = "DO";
2275             break;
2276           case TN_DONT:
2277             ddwwStr = "DONT";
2278             break;
2279           case TN_WILL:
2280             ddwwStr = "WILL";
2281             break;
2282           case TN_WONT:
2283             ddwwStr = "WONT";
2284             break;
2285           default:
2286             ddwwStr = buf1;
2287             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2288             break;
2289         }
2290         switch (option) {
2291           case TN_ECHO:
2292             optionStr = "ECHO";
2293             break;
2294           default:
2295             optionStr = buf2;
2296             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2297             break;
2298         }
2299         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2300     }
2301     msg[0] = TN_IAC;
2302     msg[1] = ddww;
2303     msg[2] = option;
2304     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2305     if (outCount < 3) {
2306         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2307     }
2308 }
2309
2310 void
2311 DoEcho ()
2312 {
2313     if (!appData.icsActive) return;
2314     TelnetRequest(TN_DO, TN_ECHO);
2315 }
2316
2317 void
2318 DontEcho ()
2319 {
2320     if (!appData.icsActive) return;
2321     TelnetRequest(TN_DONT, TN_ECHO);
2322 }
2323
2324 void
2325 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2326 {
2327     /* put the holdings sent to us by the server on the board holdings area */
2328     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2329     char p;
2330     ChessSquare piece;
2331
2332     if(gameInfo.holdingsWidth < 2)  return;
2333     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2334         return; // prevent overwriting by pre-board holdings
2335
2336     if( (int)lowestPiece >= BlackPawn ) {
2337         holdingsColumn = 0;
2338         countsColumn = 1;
2339         holdingsStartRow = BOARD_HEIGHT-1;
2340         direction = -1;
2341     } else {
2342         holdingsColumn = BOARD_WIDTH-1;
2343         countsColumn = BOARD_WIDTH-2;
2344         holdingsStartRow = 0;
2345         direction = 1;
2346     }
2347
2348     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2349         board[i][holdingsColumn] = EmptySquare;
2350         board[i][countsColumn]   = (ChessSquare) 0;
2351     }
2352     while( (p=*holdings++) != NULLCHAR ) {
2353         piece = CharToPiece( ToUpper(p) );
2354         if(piece == EmptySquare) continue;
2355         /*j = (int) piece - (int) WhitePawn;*/
2356         j = PieceToNumber(piece);
2357         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2358         if(j < 0) continue;               /* should not happen */
2359         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2360         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2361         board[holdingsStartRow+j*direction][countsColumn]++;
2362     }
2363 }
2364
2365
2366 void
2367 VariantSwitch (Board board, VariantClass newVariant)
2368 {
2369    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2370    static Board oldBoard;
2371
2372    startedFromPositionFile = FALSE;
2373    if(gameInfo.variant == newVariant) return;
2374
2375    /* [HGM] This routine is called each time an assignment is made to
2376     * gameInfo.variant during a game, to make sure the board sizes
2377     * are set to match the new variant. If that means adding or deleting
2378     * holdings, we shift the playing board accordingly
2379     * This kludge is needed because in ICS observe mode, we get boards
2380     * of an ongoing game without knowing the variant, and learn about the
2381     * latter only later. This can be because of the move list we requested,
2382     * in which case the game history is refilled from the beginning anyway,
2383     * but also when receiving holdings of a crazyhouse game. In the latter
2384     * case we want to add those holdings to the already received position.
2385     */
2386
2387
2388    if (appData.debugMode) {
2389      fprintf(debugFP, "Switch board from %s to %s\n",
2390              VariantName(gameInfo.variant), VariantName(newVariant));
2391      setbuf(debugFP, NULL);
2392    }
2393    shuffleOpenings = 0;       /* [HGM] shuffle */
2394    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2395    switch(newVariant)
2396      {
2397      case VariantShogi:
2398        newWidth = 9;  newHeight = 9;
2399        gameInfo.holdingsSize = 7;
2400      case VariantBughouse:
2401      case VariantCrazyhouse:
2402        newHoldingsWidth = 2; break;
2403      case VariantGreat:
2404        newWidth = 10;
2405      case VariantSuper:
2406        newHoldingsWidth = 2;
2407        gameInfo.holdingsSize = 8;
2408        break;
2409      case VariantGothic:
2410      case VariantCapablanca:
2411      case VariantCapaRandom:
2412        newWidth = 10;
2413      default:
2414        newHoldingsWidth = gameInfo.holdingsSize = 0;
2415      };
2416
2417    if(newWidth  != gameInfo.boardWidth  ||
2418       newHeight != gameInfo.boardHeight ||
2419       newHoldingsWidth != gameInfo.holdingsWidth ) {
2420
2421      /* shift position to new playing area, if needed */
2422      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2423        for(i=0; i<BOARD_HEIGHT; i++)
2424          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2425            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2426              board[i][j];
2427        for(i=0; i<newHeight; i++) {
2428          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2429          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2430        }
2431      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2432        for(i=0; i<BOARD_HEIGHT; i++)
2433          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2434            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2435              board[i][j];
2436      }
2437      gameInfo.boardWidth  = newWidth;
2438      gameInfo.boardHeight = newHeight;
2439      gameInfo.holdingsWidth = newHoldingsWidth;
2440      gameInfo.variant = newVariant;
2441      InitDrawingSizes(-2, 0);
2442    } else gameInfo.variant = newVariant;
2443    CopyBoard(oldBoard, board);   // remember correctly formatted board
2444      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2445    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2446 }
2447
2448 static int loggedOn = FALSE;
2449
2450 /*-- Game start info cache: --*/
2451 int gs_gamenum;
2452 char gs_kind[MSG_SIZ];
2453 static char player1Name[128] = "";
2454 static char player2Name[128] = "";
2455 static char cont_seq[] = "\n\\   ";
2456 static int player1Rating = -1;
2457 static int player2Rating = -1;
2458 /*----------------------------*/
2459
2460 ColorClass curColor = ColorNormal;
2461 int suppressKibitz = 0;
2462
2463 // [HGM] seekgraph
2464 Boolean soughtPending = FALSE;
2465 Boolean seekGraphUp;
2466 #define MAX_SEEK_ADS 200
2467 #define SQUARE 0x80
2468 char *seekAdList[MAX_SEEK_ADS];
2469 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2470 float tcList[MAX_SEEK_ADS];
2471 char colorList[MAX_SEEK_ADS];
2472 int nrOfSeekAds = 0;
2473 int minRating = 1010, maxRating = 2800;
2474 int hMargin = 10, vMargin = 20, h, w;
2475 extern int squareSize, lineGap;
2476
2477 void
2478 PlotSeekAd (int i)
2479 {
2480         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2481         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2482         if(r < minRating+100 && r >=0 ) r = minRating+100;
2483         if(r > maxRating) r = maxRating;
2484         if(tc < 1.) tc = 1.;
2485         if(tc > 95.) tc = 95.;
2486         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2487         y = ((double)r - minRating)/(maxRating - minRating)
2488             * (h-vMargin-squareSize/8-1) + vMargin;
2489         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2490         if(strstr(seekAdList[i], " u ")) color = 1;
2491         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2492            !strstr(seekAdList[i], "bullet") &&
2493            !strstr(seekAdList[i], "blitz") &&
2494            !strstr(seekAdList[i], "standard") ) color = 2;
2495         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2496         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2497 }
2498
2499 void
2500 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2501 {
2502         char buf[MSG_SIZ], *ext = "";
2503         VariantClass v = StringToVariant(type);
2504         if(strstr(type, "wild")) {
2505             ext = type + 4; // append wild number
2506             if(v == VariantFischeRandom) type = "chess960"; else
2507             if(v == VariantLoadable) type = "setup"; else
2508             type = VariantName(v);
2509         }
2510         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2511         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2512             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2513             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2514             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2515             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2516             seekNrList[nrOfSeekAds] = nr;
2517             zList[nrOfSeekAds] = 0;
2518             seekAdList[nrOfSeekAds++] = StrSave(buf);
2519             if(plot) PlotSeekAd(nrOfSeekAds-1);
2520         }
2521 }
2522
2523 void
2524 EraseSeekDot (int i)
2525 {
2526     int x = xList[i], y = yList[i], d=squareSize/4, k;
2527     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2528     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2529     // now replot every dot that overlapped
2530     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2531         int xx = xList[k], yy = yList[k];
2532         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2533             DrawSeekDot(xx, yy, colorList[k]);
2534     }
2535 }
2536
2537 void
2538 RemoveSeekAd (int nr)
2539 {
2540         int i;
2541         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2542             EraseSeekDot(i);
2543             if(seekAdList[i]) free(seekAdList[i]);
2544             seekAdList[i] = seekAdList[--nrOfSeekAds];
2545             seekNrList[i] = seekNrList[nrOfSeekAds];
2546             ratingList[i] = ratingList[nrOfSeekAds];
2547             colorList[i]  = colorList[nrOfSeekAds];
2548             tcList[i] = tcList[nrOfSeekAds];
2549             xList[i]  = xList[nrOfSeekAds];
2550             yList[i]  = yList[nrOfSeekAds];
2551             zList[i]  = zList[nrOfSeekAds];
2552             seekAdList[nrOfSeekAds] = NULL;
2553             break;
2554         }
2555 }
2556
2557 Boolean
2558 MatchSoughtLine (char *line)
2559 {
2560     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2561     int nr, base, inc, u=0; char dummy;
2562
2563     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2564        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2565        (u=1) &&
2566        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2567         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2568         // match: compact and save the line
2569         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2570         return TRUE;
2571     }
2572     return FALSE;
2573 }
2574
2575 int
2576 DrawSeekGraph ()
2577 {
2578     int i;
2579     if(!seekGraphUp) return FALSE;
2580     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2581     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2582
2583     DrawSeekBackground(0, 0, w, h);
2584     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2585     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2586     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2587         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2588         yy = h-1-yy;
2589         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2590         if(i%500 == 0) {
2591             char buf[MSG_SIZ];
2592             snprintf(buf, MSG_SIZ, "%d", i);
2593             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2594         }
2595     }
2596     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2597     for(i=1; i<100; i+=(i<10?1:5)) {
2598         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2599         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2600         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2601             char buf[MSG_SIZ];
2602             snprintf(buf, MSG_SIZ, "%d", i);
2603             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2604         }
2605     }
2606     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2607     return TRUE;
2608 }
2609
2610 int
2611 SeekGraphClick (ClickType click, int x, int y, int moving)
2612 {
2613     static int lastDown = 0, displayed = 0, lastSecond;
2614     if(y < 0) return FALSE;
2615     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2616         if(click == Release || moving) return FALSE;
2617         nrOfSeekAds = 0;
2618         soughtPending = TRUE;
2619         SendToICS(ics_prefix);
2620         SendToICS("sought\n"); // should this be "sought all"?
2621     } else { // issue challenge based on clicked ad
2622         int dist = 10000; int i, closest = 0, second = 0;
2623         for(i=0; i<nrOfSeekAds; i++) {
2624             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2625             if(d < dist) { dist = d; closest = i; }
2626             second += (d - zList[i] < 120); // count in-range ads
2627             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2628         }
2629         if(dist < 120) {
2630             char buf[MSG_SIZ];
2631             second = (second > 1);
2632             if(displayed != closest || second != lastSecond) {
2633                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2634                 lastSecond = second; displayed = closest;
2635             }
2636             if(click == Press) {
2637                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2638                 lastDown = closest;
2639                 return TRUE;
2640             } // on press 'hit', only show info
2641             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2642             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2643             SendToICS(ics_prefix);
2644             SendToICS(buf);
2645             return TRUE; // let incoming board of started game pop down the graph
2646         } else if(click == Release) { // release 'miss' is ignored
2647             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2648             if(moving == 2) { // right up-click
2649                 nrOfSeekAds = 0; // refresh graph
2650                 soughtPending = TRUE;
2651                 SendToICS(ics_prefix);
2652                 SendToICS("sought\n"); // should this be "sought all"?
2653             }
2654             return TRUE;
2655         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2656         // press miss or release hit 'pop down' seek graph
2657         seekGraphUp = FALSE;
2658         DrawPosition(TRUE, NULL);
2659     }
2660     return TRUE;
2661 }
2662
2663 void
2664 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2665 {
2666 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2667 #define STARTED_NONE 0
2668 #define STARTED_MOVES 1
2669 #define STARTED_BOARD 2
2670 #define STARTED_OBSERVE 3
2671 #define STARTED_HOLDINGS 4
2672 #define STARTED_CHATTER 5
2673 #define STARTED_COMMENT 6
2674 #define STARTED_MOVES_NOHIDE 7
2675
2676     static int started = STARTED_NONE;
2677     static char parse[20000];
2678     static int parse_pos = 0;
2679     static char buf[BUF_SIZE + 1];
2680     static int firstTime = TRUE, intfSet = FALSE;
2681     static ColorClass prevColor = ColorNormal;
2682     static int savingComment = FALSE;
2683     static int cmatch = 0; // continuation sequence match
2684     char *bp;
2685     char str[MSG_SIZ];
2686     int i, oldi;
2687     int buf_len;
2688     int next_out;
2689     int tkind;
2690     int backup;    /* [DM] For zippy color lines */
2691     char *p;
2692     char talker[MSG_SIZ]; // [HGM] chat
2693     int channel;
2694
2695     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2696
2697     if (appData.debugMode) {
2698       if (!error) {
2699         fprintf(debugFP, "<ICS: ");
2700         show_bytes(debugFP, data, count);
2701         fprintf(debugFP, "\n");
2702       }
2703     }
2704
2705     if (appData.debugMode) { int f = forwardMostMove;
2706         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2707                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2708                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2709     }
2710     if (count > 0) {
2711         /* If last read ended with a partial line that we couldn't parse,
2712            prepend it to the new read and try again. */
2713         if (leftover_len > 0) {
2714             for (i=0; i<leftover_len; i++)
2715               buf[i] = buf[leftover_start + i];
2716         }
2717
2718     /* copy new characters into the buffer */
2719     bp = buf + leftover_len;
2720     buf_len=leftover_len;
2721     for (i=0; i<count; i++)
2722     {
2723         // ignore these
2724         if (data[i] == '\r')
2725             continue;
2726
2727         // join lines split by ICS?
2728         if (!appData.noJoin)
2729         {
2730             /*
2731                 Joining just consists of finding matches against the
2732                 continuation sequence, and discarding that sequence
2733                 if found instead of copying it.  So, until a match
2734                 fails, there's nothing to do since it might be the
2735                 complete sequence, and thus, something we don't want
2736                 copied.
2737             */
2738             if (data[i] == cont_seq[cmatch])
2739             {
2740                 cmatch++;
2741                 if (cmatch == strlen(cont_seq))
2742                 {
2743                     cmatch = 0; // complete match.  just reset the counter
2744
2745                     /*
2746                         it's possible for the ICS to not include the space
2747                         at the end of the last word, making our [correct]
2748                         join operation fuse two separate words.  the server
2749                         does this when the space occurs at the width setting.
2750                     */
2751                     if (!buf_len || buf[buf_len-1] != ' ')
2752                     {
2753                         *bp++ = ' ';
2754                         buf_len++;
2755                     }
2756                 }
2757                 continue;
2758             }
2759             else if (cmatch)
2760             {
2761                 /*
2762                     match failed, so we have to copy what matched before
2763                     falling through and copying this character.  In reality,
2764                     this will only ever be just the newline character, but
2765                     it doesn't hurt to be precise.
2766                 */
2767                 strncpy(bp, cont_seq, cmatch);
2768                 bp += cmatch;
2769                 buf_len += cmatch;
2770                 cmatch = 0;
2771             }
2772         }
2773
2774         // copy this char
2775         *bp++ = data[i];
2776         buf_len++;
2777     }
2778
2779         buf[buf_len] = NULLCHAR;
2780 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2781         next_out = 0;
2782         leftover_start = 0;
2783
2784         i = 0;
2785         while (i < buf_len) {
2786             /* Deal with part of the TELNET option negotiation
2787                protocol.  We refuse to do anything beyond the
2788                defaults, except that we allow the WILL ECHO option,
2789                which ICS uses to turn off password echoing when we are
2790                directly connected to it.  We reject this option
2791                if localLineEditing mode is on (always on in xboard)
2792                and we are talking to port 23, which might be a real
2793                telnet server that will try to keep WILL ECHO on permanently.
2794              */
2795             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2796                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2797                 unsigned char option;
2798                 oldi = i;
2799                 switch ((unsigned char) buf[++i]) {
2800                   case TN_WILL:
2801                     if (appData.debugMode)
2802                       fprintf(debugFP, "\n<WILL ");
2803                     switch (option = (unsigned char) buf[++i]) {
2804                       case TN_ECHO:
2805                         if (appData.debugMode)
2806                           fprintf(debugFP, "ECHO ");
2807                         /* Reply only if this is a change, according
2808                            to the protocol rules. */
2809                         if (remoteEchoOption) break;
2810                         if (appData.localLineEditing &&
2811                             atoi(appData.icsPort) == TN_PORT) {
2812                             TelnetRequest(TN_DONT, TN_ECHO);
2813                         } else {
2814                             EchoOff();
2815                             TelnetRequest(TN_DO, TN_ECHO);
2816                             remoteEchoOption = TRUE;
2817                         }
2818                         break;
2819                       default:
2820                         if (appData.debugMode)
2821                           fprintf(debugFP, "%d ", option);
2822                         /* Whatever this is, we don't want it. */
2823                         TelnetRequest(TN_DONT, option);
2824                         break;
2825                     }
2826                     break;
2827                   case TN_WONT:
2828                     if (appData.debugMode)
2829                       fprintf(debugFP, "\n<WONT ");
2830                     switch (option = (unsigned char) buf[++i]) {
2831                       case TN_ECHO:
2832                         if (appData.debugMode)
2833                           fprintf(debugFP, "ECHO ");
2834                         /* Reply only if this is a change, according
2835                            to the protocol rules. */
2836                         if (!remoteEchoOption) break;
2837                         EchoOn();
2838                         TelnetRequest(TN_DONT, TN_ECHO);
2839                         remoteEchoOption = FALSE;
2840                         break;
2841                       default:
2842                         if (appData.debugMode)
2843                           fprintf(debugFP, "%d ", (unsigned char) option);
2844                         /* Whatever this is, it must already be turned
2845                            off, because we never agree to turn on
2846                            anything non-default, so according to the
2847                            protocol rules, we don't reply. */
2848                         break;
2849                     }
2850                     break;
2851                   case TN_DO:
2852                     if (appData.debugMode)
2853                       fprintf(debugFP, "\n<DO ");
2854                     switch (option = (unsigned char) buf[++i]) {
2855                       default:
2856                         /* Whatever this is, we refuse to do it. */
2857                         if (appData.debugMode)
2858                           fprintf(debugFP, "%d ", option);
2859                         TelnetRequest(TN_WONT, option);
2860                         break;
2861                     }
2862                     break;
2863                   case TN_DONT:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<DONT ");
2866                     switch (option = (unsigned char) buf[++i]) {
2867                       default:
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "%d ", option);
2870                         /* Whatever this is, we are already not doing
2871                            it, because we never agree to do anything
2872                            non-default, so according to the protocol
2873                            rules, we don't reply. */
2874                         break;
2875                     }
2876                     break;
2877                   case TN_IAC:
2878                     if (appData.debugMode)
2879                       fprintf(debugFP, "\n<IAC ");
2880                     /* Doubled IAC; pass it through */
2881                     i--;
2882                     break;
2883                   default:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2886                     /* Drop all other telnet commands on the floor */
2887                     break;
2888                 }
2889                 if (oldi > next_out)
2890                   SendToPlayer(&buf[next_out], oldi - next_out);
2891                 if (++i > next_out)
2892                   next_out = i;
2893                 continue;
2894             }
2895
2896             /* OK, this at least will *usually* work */
2897             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2898                 loggedOn = TRUE;
2899             }
2900
2901             if (loggedOn && !intfSet) {
2902                 if (ics_type == ICS_ICC) {
2903                   snprintf(str, MSG_SIZ,
2904                           "/set-quietly interface %s\n/set-quietly style 12\n",
2905                           programVersion);
2906                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2907                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2908                 } else if (ics_type == ICS_CHESSNET) {
2909                   snprintf(str, MSG_SIZ, "/style 12\n");
2910                 } else {
2911                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2912                   strcat(str, programVersion);
2913                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2914                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2915                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2916 #ifdef WIN32
2917                   strcat(str, "$iset nohighlight 1\n");
2918 #endif
2919                   strcat(str, "$iset lock 1\n$style 12\n");
2920                 }
2921                 SendToICS(str);
2922                 NotifyFrontendLogin();
2923                 intfSet = TRUE;
2924             }
2925
2926             if (started == STARTED_COMMENT) {
2927                 /* Accumulate characters in comment */
2928                 parse[parse_pos++] = buf[i];
2929                 if (buf[i] == '\n') {
2930                     parse[parse_pos] = NULLCHAR;
2931                     if(chattingPartner>=0) {
2932                         char mess[MSG_SIZ];
2933                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2934                         OutputChatMessage(chattingPartner, mess);
2935                         chattingPartner = -1;
2936                         next_out = i+1; // [HGM] suppress printing in ICS window
2937                     } else
2938                     if(!suppressKibitz) // [HGM] kibitz
2939                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2940                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2941                         int nrDigit = 0, nrAlph = 0, j;
2942                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2943                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2944                         parse[parse_pos] = NULLCHAR;
2945                         // try to be smart: if it does not look like search info, it should go to
2946                         // ICS interaction window after all, not to engine-output window.
2947                         for(j=0; j<parse_pos; j++) { // count letters and digits
2948                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2949                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2950                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2951                         }
2952                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2953                             int depth=0; float score;
2954                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2955                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2956                                 pvInfoList[forwardMostMove-1].depth = depth;
2957                                 pvInfoList[forwardMostMove-1].score = 100*score;
2958                             }
2959                             OutputKibitz(suppressKibitz, parse);
2960                         } else {
2961                             char tmp[MSG_SIZ];
2962                             if(gameMode == IcsObserving) // restore original ICS messages
2963                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2964                             else
2965                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2966                             SendToPlayer(tmp, strlen(tmp));
2967                         }
2968                         next_out = i+1; // [HGM] suppress printing in ICS window
2969                     }
2970                     started = STARTED_NONE;
2971                 } else {
2972                     /* Don't match patterns against characters in comment */
2973                     i++;
2974                     continue;
2975                 }
2976             }
2977             if (started == STARTED_CHATTER) {
2978                 if (buf[i] != '\n') {
2979                     /* Don't match patterns against characters in chatter */
2980                     i++;
2981                     continue;
2982                 }
2983                 started = STARTED_NONE;
2984                 if(suppressKibitz) next_out = i+1;
2985             }
2986
2987             /* Kludge to deal with rcmd protocol */
2988             if (firstTime && looking_at(buf, &i, "\001*")) {
2989                 DisplayFatalError(&buf[1], 0, 1);
2990                 continue;
2991             } else {
2992                 firstTime = FALSE;
2993             }
2994
2995             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2996                 ics_type = ICS_ICC;
2997                 ics_prefix = "/";
2998                 if (appData.debugMode)
2999                   fprintf(debugFP, "ics_type %d\n", ics_type);
3000                 continue;
3001             }
3002             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3003                 ics_type = ICS_FICS;
3004                 ics_prefix = "$";
3005                 if (appData.debugMode)
3006                   fprintf(debugFP, "ics_type %d\n", ics_type);
3007                 continue;
3008             }
3009             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3010                 ics_type = ICS_CHESSNET;
3011                 ics_prefix = "/";
3012                 if (appData.debugMode)
3013                   fprintf(debugFP, "ics_type %d\n", ics_type);
3014                 continue;
3015             }
3016
3017             if (!loggedOn &&
3018                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3019                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3020                  looking_at(buf, &i, "will be \"*\""))) {
3021               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3022               continue;
3023             }
3024
3025             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3026               char buf[MSG_SIZ];
3027               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3028               DisplayIcsInteractionTitle(buf);
3029               have_set_title = TRUE;
3030             }
3031
3032             /* skip finger notes */
3033             if (started == STARTED_NONE &&
3034                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3035                  (buf[i] == '1' && buf[i+1] == '0')) &&
3036                 buf[i+2] == ':' && buf[i+3] == ' ') {
3037               started = STARTED_CHATTER;
3038               i += 3;
3039               continue;
3040             }
3041
3042             oldi = i;
3043             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3044             if(appData.seekGraph) {
3045                 if(soughtPending && MatchSoughtLine(buf+i)) {
3046                     i = strstr(buf+i, "rated") - buf;
3047                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3048                     next_out = leftover_start = i;
3049                     started = STARTED_CHATTER;
3050                     suppressKibitz = TRUE;
3051                     continue;
3052                 }
3053                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3054                         && looking_at(buf, &i, "* ads displayed")) {
3055                     soughtPending = FALSE;
3056                     seekGraphUp = TRUE;
3057                     DrawSeekGraph();
3058                     continue;
3059                 }
3060                 if(appData.autoRefresh) {
3061                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3062                         int s = (ics_type == ICS_ICC); // ICC format differs
3063                         if(seekGraphUp)
3064                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3065                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3066                         looking_at(buf, &i, "*% "); // eat prompt
3067                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3068                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3069                         next_out = i; // suppress
3070                         continue;
3071                     }
3072                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3073                         char *p = star_match[0];
3074                         while(*p) {
3075                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3076                             while(*p && *p++ != ' '); // next
3077                         }
3078                         looking_at(buf, &i, "*% "); // eat prompt
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i;
3081                         continue;
3082                     }
3083                 }
3084             }
3085
3086             /* skip formula vars */
3087             if (started == STARTED_NONE &&
3088                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3089               started = STARTED_CHATTER;
3090               i += 3;
3091               continue;
3092             }
3093
3094             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3095             if (appData.autoKibitz && started == STARTED_NONE &&
3096                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3097                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3098                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3099                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3100                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3101                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3102                         suppressKibitz = TRUE;
3103                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3104                         next_out = i;
3105                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3106                                 && (gameMode == IcsPlayingWhite)) ||
3107                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3108                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3109                             started = STARTED_CHATTER; // own kibitz we simply discard
3110                         else {
3111                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3112                             parse_pos = 0; parse[0] = NULLCHAR;
3113                             savingComment = TRUE;
3114                             suppressKibitz = gameMode != IcsObserving ? 2 :
3115                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3116                         }
3117                         continue;
3118                 } else
3119                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3120                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3121                          && atoi(star_match[0])) {
3122                     // suppress the acknowledgements of our own autoKibitz
3123                     char *p;
3124                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3125                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3126                     SendToPlayer(star_match[0], strlen(star_match[0]));
3127                     if(looking_at(buf, &i, "*% ")) // eat prompt
3128                         suppressKibitz = FALSE;
3129                     next_out = i;
3130                     continue;
3131                 }
3132             } // [HGM] kibitz: end of patch
3133
3134             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3135
3136             // [HGM] chat: intercept tells by users for which we have an open chat window
3137             channel = -1;
3138             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3139                                            looking_at(buf, &i, "* whispers:") ||
3140                                            looking_at(buf, &i, "* kibitzes:") ||
3141                                            looking_at(buf, &i, "* shouts:") ||
3142                                            looking_at(buf, &i, "* c-shouts:") ||
3143                                            looking_at(buf, &i, "--> * ") ||
3144                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3148                 int p;
3149                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3150                 chattingPartner = -1;
3151
3152                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3153                 for(p=0; p<MAX_CHAT; p++) {
3154                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3155                     talker[0] = '['; strcat(talker, "] ");
3156                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3157                     chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("kibitzes", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3168                 for(p=0; p<MAX_CHAT; p++) {
3169                     if(!strcmp("whispers", chatPartner[p])) {
3170                         talker[0] = '['; strcat(talker, "] ");
3171                         chattingPartner = p; break;
3172                     }
3173                 } else
3174                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3175                   if(buf[i-8] == '-' && buf[i-3] == 't')
3176                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3177                     if(!strcmp("c-shouts", chatPartner[p])) {
3178                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3179                         chattingPartner = p; break;
3180                     }
3181                   }
3182                   if(chattingPartner < 0)
3183                   for(p=0; p<MAX_CHAT; p++) {
3184                     if(!strcmp("shouts", chatPartner[p])) {
3185                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3186                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3187                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3188                         chattingPartner = p; break;
3189                     }
3190                   }
3191                 }
3192                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3193                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3194                     talker[0] = 0; Colorize(ColorTell, FALSE);
3195                     chattingPartner = p; break;
3196                 }
3197                 if(chattingPartner<0) i = oldi; else {
3198                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3199                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3200                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201                     started = STARTED_COMMENT;
3202                     parse_pos = 0; parse[0] = NULLCHAR;
3203                     savingComment = 3 + chattingPartner; // counts as TRUE
3204                     suppressKibitz = TRUE;
3205                     continue;
3206                 }
3207             } // [HGM] chat: end of patch
3208
3209           backup = i;
3210             if (appData.zippyTalk || appData.zippyPlay) {
3211                 /* [DM] Backup address for color zippy lines */
3212 #if ZIPPY
3213                if (loggedOn == TRUE)
3214                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3215                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3216 #endif
3217             } // [DM] 'else { ' deleted
3218                 if (
3219                     /* Regular tells and says */
3220                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3221                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3222                     looking_at(buf, &i, "* says: ") ||
3223                     /* Don't color "message" or "messages" output */
3224                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3225                     looking_at(buf, &i, "*. * at *:*: ") ||
3226                     looking_at(buf, &i, "--* (*:*): ") ||
3227                     /* Message notifications (same color as tells) */
3228                     looking_at(buf, &i, "* has left a message ") ||
3229                     looking_at(buf, &i, "* just sent you a message:\n") ||
3230                     /* Whispers and kibitzes */
3231                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3232                     looking_at(buf, &i, "* kibitzes: ") ||
3233                     /* Channel tells */
3234                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3235
3236                   if (tkind == 1 && strchr(star_match[0], ':')) {
3237                       /* Avoid "tells you:" spoofs in channels */
3238                      tkind = 3;
3239                   }
3240                   if (star_match[0][0] == NULLCHAR ||
3241                       strchr(star_match[0], ' ') ||
3242                       (tkind == 3 && strchr(star_match[1], ' '))) {
3243                     /* Reject bogus matches */
3244                     i = oldi;
3245                   } else {
3246                     if (appData.colorize) {
3247                       if (oldi > next_out) {
3248                         SendToPlayer(&buf[next_out], oldi - next_out);
3249                         next_out = oldi;
3250                       }
3251                       switch (tkind) {
3252                       case 1:
3253                         Colorize(ColorTell, FALSE);
3254                         curColor = ColorTell;
3255                         break;
3256                       case 2:
3257                         Colorize(ColorKibitz, FALSE);
3258                         curColor = ColorKibitz;
3259                         break;
3260                       case 3:
3261                         p = strrchr(star_match[1], '(');
3262                         if (p == NULL) {
3263                           p = star_match[1];
3264                         } else {
3265                           p++;
3266                         }
3267                         if (atoi(p) == 1) {
3268                           Colorize(ColorChannel1, FALSE);
3269                           curColor = ColorChannel1;
3270                         } else {
3271                           Colorize(ColorChannel, FALSE);
3272                           curColor = ColorChannel;
3273                         }
3274                         break;
3275                       case 5:
3276                         curColor = ColorNormal;
3277                         break;
3278                       }
3279                     }
3280                     if (started == STARTED_NONE && appData.autoComment &&
3281                         (gameMode == IcsObserving ||
3282                          gameMode == IcsPlayingWhite ||
3283                          gameMode == IcsPlayingBlack)) {
3284                       parse_pos = i - oldi;
3285                       memcpy(parse, &buf[oldi], parse_pos);
3286                       parse[parse_pos] = NULLCHAR;
3287                       started = STARTED_COMMENT;
3288                       savingComment = TRUE;
3289                     } else {
3290                       started = STARTED_CHATTER;
3291                       savingComment = FALSE;
3292                     }
3293                     loggedOn = TRUE;
3294                     continue;
3295                   }
3296                 }
3297
3298                 if (looking_at(buf, &i, "* s-shouts: ") ||
3299                     looking_at(buf, &i, "* c-shouts: ")) {
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorSShout, FALSE);
3306                         curColor = ColorSShout;
3307                     }
3308                     loggedOn = TRUE;
3309                     started = STARTED_CHATTER;
3310                     continue;
3311                 }
3312
3313                 if (looking_at(buf, &i, "--->")) {
3314                     loggedOn = TRUE;
3315                     continue;
3316                 }
3317
3318                 if (looking_at(buf, &i, "* shouts: ") ||
3319                     looking_at(buf, &i, "--> ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorShout, FALSE);
3326                         curColor = ColorShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at( buf, &i, "Challenge:")) {
3334                     if (appData.colorize) {
3335                         if (oldi > next_out) {
3336                             SendToPlayer(&buf[next_out], oldi - next_out);
3337                             next_out = oldi;
3338                         }
3339                         Colorize(ColorChallenge, FALSE);
3340                         curColor = ColorChallenge;
3341                     }
3342                     loggedOn = TRUE;
3343                     continue;
3344                 }
3345
3346                 if (looking_at(buf, &i, "* offers you") ||
3347                     looking_at(buf, &i, "* offers to be") ||
3348                     looking_at(buf, &i, "* would like to") ||
3349                     looking_at(buf, &i, "* requests to") ||
3350                     looking_at(buf, &i, "Your opponent offers") ||
3351                     looking_at(buf, &i, "Your opponent requests")) {
3352
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorRequest, FALSE);
3359                         curColor = ColorRequest;
3360                     }
3361                     continue;
3362                 }
3363
3364                 if (looking_at(buf, &i, "* (*) seeking")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorSeek, FALSE);
3371                         curColor = ColorSeek;
3372                     }
3373                     continue;
3374             }
3375
3376           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3377
3378             if (looking_at(buf, &i, "\\   ")) {
3379                 if (prevColor != ColorNormal) {
3380                     if (oldi > next_out) {
3381                         SendToPlayer(&buf[next_out], oldi - next_out);
3382                         next_out = oldi;
3383                     }
3384                     Colorize(prevColor, TRUE);
3385                     curColor = prevColor;
3386                 }
3387                 if (savingComment) {
3388                     parse_pos = i - oldi;
3389                     memcpy(parse, &buf[oldi], parse_pos);
3390                     parse[parse_pos] = NULLCHAR;
3391                     started = STARTED_COMMENT;
3392                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3393                         chattingPartner = savingComment - 3; // kludge to remember the box
3394                 } else {
3395                     started = STARTED_CHATTER;
3396                 }
3397                 continue;
3398             }
3399
3400             if (looking_at(buf, &i, "Black Strength :") ||
3401                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3402                 looking_at(buf, &i, "<10>") ||
3403                 looking_at(buf, &i, "#@#")) {
3404                 /* Wrong board style */
3405                 loggedOn = TRUE;
3406                 SendToICS(ics_prefix);
3407                 SendToICS("set style 12\n");
3408                 SendToICS(ics_prefix);
3409                 SendToICS("refresh\n");
3410                 continue;
3411             }
3412
3413             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3414                 ICSInitScript();
3415                 have_sent_ICS_logon = 1;
3416                 continue;
3417             }
3418
3419             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3420                 (looking_at(buf, &i, "\n<12> ") ||
3421                  looking_at(buf, &i, "<12> "))) {
3422                 loggedOn = TRUE;
3423                 if (oldi > next_out) {
3424                     SendToPlayer(&buf[next_out], oldi - next_out);
3425                 }
3426                 next_out = i;
3427                 started = STARTED_BOARD;
3428                 parse_pos = 0;
3429                 continue;
3430             }
3431
3432             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3433                 looking_at(buf, &i, "<b1> ")) {
3434                 if (oldi > next_out) {
3435                     SendToPlayer(&buf[next_out], oldi - next_out);
3436                 }
3437                 next_out = i;
3438                 started = STARTED_HOLDINGS;
3439                 parse_pos = 0;
3440                 continue;
3441             }
3442
3443             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3444                 loggedOn = TRUE;
3445                 /* Header for a move list -- first line */
3446
3447                 switch (ics_getting_history) {
3448                   case H_FALSE:
3449                     switch (gameMode) {
3450                       case IcsIdle:
3451                       case BeginningOfGame:
3452                         /* User typed "moves" or "oldmoves" while we
3453                            were idle.  Pretend we asked for these
3454                            moves and soak them up so user can step
3455                            through them and/or save them.
3456                            */
3457                         Reset(FALSE, TRUE);
3458                         gameMode = IcsObserving;
3459                         ModeHighlight();
3460                         ics_gamenum = -1;
3461                         ics_getting_history = H_GOT_UNREQ_HEADER;
3462                         break;
3463                       case EditGame: /*?*/
3464                       case EditPosition: /*?*/
3465                         /* Should above feature work in these modes too? */
3466                         /* For now it doesn't */
3467                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3468                         break;
3469                       default:
3470                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3471                         break;
3472                     }
3473                     break;
3474                   case H_REQUESTED:
3475                     /* Is this the right one? */
3476                     if (gameInfo.white && gameInfo.black &&
3477                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3478                         strcmp(gameInfo.black, star_match[2]) == 0) {
3479                         /* All is well */
3480                         ics_getting_history = H_GOT_REQ_HEADER;
3481                     }
3482                     break;
3483                   case H_GOT_REQ_HEADER:
3484                   case H_GOT_UNREQ_HEADER:
3485                   case H_GOT_UNWANTED_HEADER:
3486                   case H_GETTING_MOVES:
3487                     /* Should not happen */
3488                     DisplayError(_("Error gathering move list: two headers"), 0);
3489                     ics_getting_history = H_FALSE;
3490                     break;
3491                 }
3492
3493                 /* Save player ratings into gameInfo if needed */
3494                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3495                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3496                     (gameInfo.whiteRating == -1 ||
3497                      gameInfo.blackRating == -1)) {
3498
3499                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3500                     gameInfo.blackRating = string_to_rating(star_match[3]);
3501                     if (appData.debugMode)
3502                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3503                               gameInfo.whiteRating, gameInfo.blackRating);
3504                 }
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i,
3509               "* * match, initial time: * minute*, increment: * second")) {
3510                 /* Header for a move list -- second line */
3511                 /* Initial board will follow if this is a wild game */
3512                 if (gameInfo.event != NULL) free(gameInfo.event);
3513                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3514                 gameInfo.event = StrSave(str);
3515                 /* [HGM] we switched variant. Translate boards if needed. */
3516                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3517                 continue;
3518             }
3519
3520             if (looking_at(buf, &i, "Move  ")) {
3521                 /* Beginning of a move list */
3522                 switch (ics_getting_history) {
3523                   case H_FALSE:
3524                     /* Normally should not happen */
3525                     /* Maybe user hit reset while we were parsing */
3526                     break;
3527                   case H_REQUESTED:
3528                     /* Happens if we are ignoring a move list that is not
3529                      * the one we just requested.  Common if the user
3530                      * tries to observe two games without turning off
3531                      * getMoveList */
3532                     break;
3533                   case H_GETTING_MOVES:
3534                     /* Should not happen */
3535                     DisplayError(_("Error gathering move list: nested"), 0);
3536                     ics_getting_history = H_FALSE;
3537                     break;
3538                   case H_GOT_REQ_HEADER:
3539                     ics_getting_history = H_GETTING_MOVES;
3540                     started = STARTED_MOVES;
3541                     parse_pos = 0;
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                     }
3545                     break;
3546                   case H_GOT_UNREQ_HEADER:
3547                     ics_getting_history = H_GETTING_MOVES;
3548                     started = STARTED_MOVES_NOHIDE;
3549                     parse_pos = 0;
3550                     break;
3551                   case H_GOT_UNWANTED_HEADER:
3552                     ics_getting_history = H_FALSE;
3553                     break;
3554                 }
3555                 continue;
3556             }
3557
3558             if (looking_at(buf, &i, "% ") ||
3559                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3560                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3561                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3562                     soughtPending = FALSE;
3563                     seekGraphUp = TRUE;
3564                     DrawSeekGraph();
3565                 }
3566                 if(suppressKibitz) next_out = i;
3567                 savingComment = FALSE;
3568                 suppressKibitz = 0;
3569                 switch (started) {
3570                   case STARTED_MOVES:
3571                   case STARTED_MOVES_NOHIDE:
3572                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3573                     parse[parse_pos + i - oldi] = NULLCHAR;
3574                     ParseGameHistory(parse);
3575 #if ZIPPY
3576                     if (appData.zippyPlay && first.initDone) {
3577                         FeedMovesToProgram(&first, forwardMostMove);
3578                         if (gameMode == IcsPlayingWhite) {
3579                             if (WhiteOnMove(forwardMostMove)) {
3580                                 if (first.sendTime) {
3581                                   if (first.useColors) {
3582                                     SendToProgram("black\n", &first);
3583                                   }
3584                                   SendTimeRemaining(&first, TRUE);
3585                                 }
3586                                 if (first.useColors) {
3587                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3588                                 }
3589                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3590                                 first.maybeThinking = TRUE;
3591                             } else {
3592                                 if (first.usePlayother) {
3593                                   if (first.sendTime) {
3594                                     SendTimeRemaining(&first, TRUE);
3595                                   }
3596                                   SendToProgram("playother\n", &first);
3597                                   firstMove = FALSE;
3598                                 } else {
3599                                   firstMove = TRUE;
3600                                 }
3601                             }
3602                         } else if (gameMode == IcsPlayingBlack) {
3603                             if (!WhiteOnMove(forwardMostMove)) {
3604                                 if (first.sendTime) {
3605                                   if (first.useColors) {
3606                                     SendToProgram("white\n", &first);
3607                                   }
3608                                   SendTimeRemaining(&first, FALSE);
3609                                 }
3610                                 if (first.useColors) {
3611                                   SendToProgram("black\n", &first);
3612                                 }
3613                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3614                                 first.maybeThinking = TRUE;
3615                             } else {
3616                                 if (first.usePlayother) {
3617                                   if (first.sendTime) {
3618                                     SendTimeRemaining(&first, FALSE);
3619                                   }
3620                                   SendToProgram("playother\n", &first);
3621                                   firstMove = FALSE;
3622                                 } else {
3623                                   firstMove = TRUE;
3624                                 }
3625                             }
3626                         }
3627                     }
3628 #endif
3629                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3630                         /* Moves came from oldmoves or moves command
3631                            while we weren't doing anything else.
3632                            */
3633                         currentMove = forwardMostMove;
3634                         ClearHighlights();/*!!could figure this out*/
3635                         flipView = appData.flipView;
3636                         DrawPosition(TRUE, boards[currentMove]);
3637                         DisplayBothClocks();
3638                         snprintf(str, MSG_SIZ, "%s %s %s",
3639                                 gameInfo.white, _("vs."),  gameInfo.black);
3640                         DisplayTitle(str);
3641                         gameMode = IcsIdle;
3642                     } else {
3643                         /* Moves were history of an active game */
3644                         if (gameInfo.resultDetails != NULL) {
3645                             free(gameInfo.resultDetails);
3646                             gameInfo.resultDetails = NULL;
3647                         }
3648                     }
3649                     HistorySet(parseList, backwardMostMove,
3650                                forwardMostMove, currentMove-1);
3651                     DisplayMove(currentMove - 1);
3652                     if (started == STARTED_MOVES) next_out = i;
3653                     started = STARTED_NONE;
3654                     ics_getting_history = H_FALSE;
3655                     break;
3656
3657                   case STARTED_OBSERVE:
3658                     started = STARTED_NONE;
3659                     SendToICS(ics_prefix);
3660                     SendToICS("refresh\n");
3661                     break;
3662
3663                   default:
3664                     break;
3665                 }
3666                 if(bookHit) { // [HGM] book: simulate book reply
3667                     static char bookMove[MSG_SIZ]; // a bit generous?
3668
3669                     programStats.nodes = programStats.depth = programStats.time =
3670                     programStats.score = programStats.got_only_move = 0;
3671                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3672
3673                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3674                     strcat(bookMove, bookHit);
3675                     HandleMachineMove(bookMove, &first);
3676                 }
3677                 continue;
3678             }
3679
3680             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3681                  started == STARTED_HOLDINGS ||
3682                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3683                 /* Accumulate characters in move list or board */
3684                 parse[parse_pos++] = buf[i];
3685             }
3686
3687             /* Start of game messages.  Mostly we detect start of game
3688                when the first board image arrives.  On some versions
3689                of the ICS, though, we need to do a "refresh" after starting
3690                to observe in order to get the current board right away. */
3691             if (looking_at(buf, &i, "Adding game * to observation list")) {
3692                 started = STARTED_OBSERVE;
3693                 continue;
3694             }
3695
3696             /* Handle auto-observe */
3697             if (appData.autoObserve &&
3698                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3699                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3700                 char *player;
3701                 /* Choose the player that was highlighted, if any. */
3702                 if (star_match[0][0] == '\033' ||
3703                     star_match[1][0] != '\033') {
3704                     player = star_match[0];
3705                 } else {
3706                     player = star_match[2];
3707                 }
3708                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3709                         ics_prefix, StripHighlightAndTitle(player));
3710                 SendToICS(str);
3711
3712                 /* Save ratings from notify string */
3713                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3714                 player1Rating = string_to_rating(star_match[1]);
3715                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3716                 player2Rating = string_to_rating(star_match[3]);
3717
3718                 if (appData.debugMode)
3719                   fprintf(debugFP,
3720                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3721                           player1Name, player1Rating,
3722                           player2Name, player2Rating);
3723
3724                 continue;
3725             }
3726
3727             /* Deal with automatic examine mode after a game,
3728                and with IcsObserving -> IcsExamining transition */
3729             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3730                 looking_at(buf, &i, "has made you an examiner of game *")) {
3731
3732                 int gamenum = atoi(star_match[0]);
3733                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3734                     gamenum == ics_gamenum) {
3735                     /* We were already playing or observing this game;
3736                        no need to refetch history */
3737                     gameMode = IcsExamining;
3738                     if (pausing) {
3739                         pauseExamForwardMostMove = forwardMostMove;
3740                     } else if (currentMove < forwardMostMove) {
3741                         ForwardInner(forwardMostMove);
3742                     }
3743                 } else {
3744                     /* I don't think this case really can happen */
3745                     SendToICS(ics_prefix);
3746                     SendToICS("refresh\n");
3747                 }
3748                 continue;
3749             }
3750
3751             /* Error messages */
3752 //          if (ics_user_moved) {
3753             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3754                 if (looking_at(buf, &i, "Illegal move") ||
3755                     looking_at(buf, &i, "Not a legal move") ||
3756                     looking_at(buf, &i, "Your king is in check") ||
3757                     looking_at(buf, &i, "It isn't your turn") ||
3758                     looking_at(buf, &i, "It is not your move")) {
3759                     /* Illegal move */
3760                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3761                         currentMove = forwardMostMove-1;
3762                         DisplayMove(currentMove - 1); /* before DMError */
3763                         DrawPosition(FALSE, boards[currentMove]);
3764                         SwitchClocks(forwardMostMove-1); // [HGM] race
3765                         DisplayBothClocks();
3766                     }
3767                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3768                     ics_user_moved = 0;
3769                     continue;
3770                 }
3771             }
3772
3773             if (looking_at(buf, &i, "still have time") ||
3774                 looking_at(buf, &i, "not out of time") ||
3775                 looking_at(buf, &i, "either player is out of time") ||
3776                 looking_at(buf, &i, "has timeseal; checking")) {
3777                 /* We must have called his flag a little too soon */
3778                 whiteFlag = blackFlag = FALSE;
3779                 continue;
3780             }
3781
3782             if (looking_at(buf, &i, "added * seconds to") ||
3783                 looking_at(buf, &i, "seconds were added to")) {
3784                 /* Update the clocks */
3785                 SendToICS(ics_prefix);
3786                 SendToICS("refresh\n");
3787                 continue;
3788             }
3789
3790             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3791                 ics_clock_paused = TRUE;
3792                 StopClocks();
3793                 continue;
3794             }
3795
3796             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3797                 ics_clock_paused = FALSE;
3798                 StartClocks();
3799                 continue;
3800             }
3801
3802             /* Grab player ratings from the Creating: message.
3803                Note we have to check for the special case when
3804                the ICS inserts things like [white] or [black]. */
3805             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3806                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3807                 /* star_matches:
3808                    0    player 1 name (not necessarily white)
3809                    1    player 1 rating
3810                    2    empty, white, or black (IGNORED)
3811                    3    player 2 name (not necessarily black)
3812                    4    player 2 rating
3813
3814                    The names/ratings are sorted out when the game
3815                    actually starts (below).
3816                 */
3817                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3818                 player1Rating = string_to_rating(star_match[1]);
3819                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3820                 player2Rating = string_to_rating(star_match[4]);
3821
3822                 if (appData.debugMode)
3823                   fprintf(debugFP,
3824                           "Ratings from 'Creating:' %s %d, %s %d\n",
3825                           player1Name, player1Rating,
3826                           player2Name, player2Rating);
3827
3828                 continue;
3829             }
3830
3831             /* Improved generic start/end-of-game messages */
3832             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3833                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3834                 /* If tkind == 0: */
3835                 /* star_match[0] is the game number */
3836                 /*           [1] is the white player's name */
3837                 /*           [2] is the black player's name */
3838                 /* For end-of-game: */
3839                 /*           [3] is the reason for the game end */
3840                 /*           [4] is a PGN end game-token, preceded by " " */
3841                 /* For start-of-game: */
3842                 /*           [3] begins with "Creating" or "Continuing" */
3843                 /*           [4] is " *" or empty (don't care). */
3844                 int gamenum = atoi(star_match[0]);
3845                 char *whitename, *blackname, *why, *endtoken;
3846                 ChessMove endtype = EndOfFile;
3847
3848                 if (tkind == 0) {
3849                   whitename = star_match[1];
3850                   blackname = star_match[2];
3851                   why = star_match[3];
3852                   endtoken = star_match[4];
3853                 } else {
3854                   whitename = star_match[1];
3855                   blackname = star_match[3];
3856                   why = star_match[5];
3857                   endtoken = star_match[6];
3858                 }
3859
3860                 /* Game start messages */
3861                 if (strncmp(why, "Creating ", 9) == 0 ||
3862                     strncmp(why, "Continuing ", 11) == 0) {