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