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