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