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