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