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