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