Refactor code for loading games and positions in match
[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 int
1235 CalculateIndex(int index, int gameNr)
1236 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1237     int res;
1238     if(index > 0) return index; // fixed nmber
1239     if(index == 0) return 1;
1240     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1241     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1242     return res;
1243 }
1244
1245 int
1246 LoadGameOrPosition(int gameNr)
1247 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1248     if (*appData.loadGameFile != NULLCHAR) {
1249         if (!LoadGameFromFile(appData.loadGameFile,
1250                 CalculateIndex(appData.loadGameIndex, gameNr),
1251                               appData.loadGameFile, FALSE)) {
1252             DisplayFatalError(_("Bad game file"), 0, 1);
1253             return 0;
1254         }
1255     } else if (*appData.loadPositionFile != NULLCHAR) {
1256         if (!LoadPositionFromFile(appData.loadPositionFile,
1257                 CalculateIndex(appData.loadPositionIndex, gameNr),
1258                                   appData.loadPositionFile)) {
1259             DisplayFatalError(_("Bad position file"), 0, 1);
1260             return 0;
1261         }
1262     }
1263     return 1;
1264 }
1265
1266 void
1267 MatchEvent(int mode)
1268 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1269         /* Set up machine vs. machine match */
1270         if (appData.noChessProgram) {
1271             DisplayFatalError(_("Can't have a match with no chess programs"),
1272                               0, 2);
1273             return;
1274         }
1275         matchMode = mode;
1276         matchGame = 1;
1277         if(!LoadGameOrPositionFile(1)) return;
1278         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1279         TwoMachinesEvent();
1280 }
1281
1282 void
1283 InitBackEnd3 P((void))
1284 {
1285     GameMode initialMode;
1286     char buf[MSG_SIZ];
1287     int err, len;
1288
1289     InitChessProgram(&first, startedFromSetupPosition);
1290
1291     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1292         free(programVersion);
1293         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1294         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1295     }
1296
1297     if (appData.icsActive) {
1298 #ifdef WIN32
1299         /* [DM] Make a console window if needed [HGM] merged ifs */
1300         ConsoleCreate();
1301 #endif
1302         err = establish();
1303         if (err != 0)
1304           {
1305             if (*appData.icsCommPort != NULLCHAR)
1306               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1307                              appData.icsCommPort);
1308             else
1309               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1310                         appData.icsHost, appData.icsPort);
1311
1312             if( (len > MSG_SIZ) && appData.debugMode )
1313               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1314
1315             DisplayFatalError(buf, err, 1);
1316             return;
1317         }
1318         SetICSMode();
1319         telnetISR =
1320           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1321         fromUserISR =
1322           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1323         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1324             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1325     } else if (appData.noChessProgram) {
1326         SetNCPMode();
1327     } else {
1328         SetGNUMode();
1329     }
1330
1331     if (*appData.cmailGameName != NULLCHAR) {
1332         SetCmailMode();
1333         OpenLoopback(&cmailPR);
1334         cmailISR =
1335           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1336     }
1337
1338     ThawUI();
1339     DisplayMessage("", "");
1340     if (StrCaseCmp(appData.initialMode, "") == 0) {
1341       initialMode = BeginningOfGame;
1342       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1343         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1344         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1345         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1346         ModeHighlight();
1347       }
1348     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1349       initialMode = TwoMachinesPlay;
1350     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1351       initialMode = AnalyzeFile;
1352     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1353       initialMode = AnalyzeMode;
1354     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1355       initialMode = MachinePlaysWhite;
1356     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1357       initialMode = MachinePlaysBlack;
1358     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1359       initialMode = EditGame;
1360     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1361       initialMode = EditPosition;
1362     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1363       initialMode = Training;
1364     } else {
1365       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1366       if( (len > MSG_SIZ) && appData.debugMode )
1367         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1368
1369       DisplayFatalError(buf, 0, 2);
1370       return;
1371     }
1372
1373     if (appData.matchMode) {
1374         MatchEvent(TRUE);
1375     } else if (*appData.cmailGameName != NULLCHAR) {
1376         /* Set up cmail mode */
1377         ReloadCmailMsgEvent(TRUE);
1378     } else {
1379         /* Set up other modes */
1380         if (initialMode == AnalyzeFile) {
1381           if (*appData.loadGameFile == NULLCHAR) {
1382             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1383             return;
1384           }
1385         }
1386         if (*appData.loadGameFile != NULLCHAR) {
1387             (void) LoadGameFromFile(appData.loadGameFile,
1388                                     appData.loadGameIndex,
1389                                     appData.loadGameFile, TRUE);
1390         } else if (*appData.loadPositionFile != NULLCHAR) {
1391             (void) LoadPositionFromFile(appData.loadPositionFile,
1392                                         appData.loadPositionIndex,
1393                                         appData.loadPositionFile);
1394             /* [HGM] try to make self-starting even after FEN load */
1395             /* to allow automatic setup of fairy variants with wtm */
1396             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1397                 gameMode = BeginningOfGame;
1398                 setboardSpoiledMachineBlack = 1;
1399             }
1400             /* [HGM] loadPos: make that every new game uses the setup */
1401             /* from file as long as we do not switch variant          */
1402             if(!blackPlaysFirst) {
1403                 startedFromPositionFile = TRUE;
1404                 CopyBoard(filePosition, boards[0]);
1405             }
1406         }
1407         if (initialMode == AnalyzeMode) {
1408           if (appData.noChessProgram) {
1409             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1410             return;
1411           }
1412           if (appData.icsActive) {
1413             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1414             return;
1415           }
1416           AnalyzeModeEvent();
1417         } else if (initialMode == AnalyzeFile) {
1418           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1419           ShowThinkingEvent();
1420           AnalyzeFileEvent();
1421           AnalysisPeriodicEvent(1);
1422         } else if (initialMode == MachinePlaysWhite) {
1423           if (appData.noChessProgram) {
1424             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1425                               0, 2);
1426             return;
1427           }
1428           if (appData.icsActive) {
1429             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1430                               0, 2);
1431             return;
1432           }
1433           MachineWhiteEvent();
1434         } else if (initialMode == MachinePlaysBlack) {
1435           if (appData.noChessProgram) {
1436             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1437                               0, 2);
1438             return;
1439           }
1440           if (appData.icsActive) {
1441             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1442                               0, 2);
1443             return;
1444           }
1445           MachineBlackEvent();
1446         } else if (initialMode == TwoMachinesPlay) {
1447           if (appData.noChessProgram) {
1448             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1449                               0, 2);
1450             return;
1451           }
1452           if (appData.icsActive) {
1453             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1454                               0, 2);
1455             return;
1456           }
1457           TwoMachinesEvent();
1458         } else if (initialMode == EditGame) {
1459           EditGameEvent();
1460         } else if (initialMode == EditPosition) {
1461           EditPositionEvent();
1462         } else if (initialMode == Training) {
1463           if (*appData.loadGameFile == NULLCHAR) {
1464             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1465             return;
1466           }
1467           TrainingEvent();
1468         }
1469     }
1470 }
1471
1472 /*
1473  * Establish will establish a contact to a remote host.port.
1474  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1475  *  used to talk to the host.
1476  * Returns 0 if okay, error code if not.
1477  */
1478 int
1479 establish()
1480 {
1481     char buf[MSG_SIZ];
1482
1483     if (*appData.icsCommPort != NULLCHAR) {
1484         /* Talk to the host through a serial comm port */
1485         return OpenCommPort(appData.icsCommPort, &icsPR);
1486
1487     } else if (*appData.gateway != NULLCHAR) {
1488         if (*appData.remoteShell == NULLCHAR) {
1489             /* Use the rcmd protocol to run telnet program on a gateway host */
1490             snprintf(buf, sizeof(buf), "%s %s %s",
1491                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1492             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1493
1494         } else {
1495             /* Use the rsh program to run telnet program on a gateway host */
1496             if (*appData.remoteUser == NULLCHAR) {
1497                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1498                         appData.gateway, appData.telnetProgram,
1499                         appData.icsHost, appData.icsPort);
1500             } else {
1501                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1502                         appData.remoteShell, appData.gateway,
1503                         appData.remoteUser, appData.telnetProgram,
1504                         appData.icsHost, appData.icsPort);
1505             }
1506             return StartChildProcess(buf, "", &icsPR);
1507
1508         }
1509     } else if (appData.useTelnet) {
1510         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1511
1512     } else {
1513         /* TCP socket interface differs somewhat between
1514            Unix and NT; handle details in the front end.
1515            */
1516         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1517     }
1518 }
1519
1520 void EscapeExpand(char *p, char *q)
1521 {       // [HGM] initstring: routine to shape up string arguments
1522         while(*p++ = *q++) if(p[-1] == '\\')
1523             switch(*q++) {
1524                 case 'n': p[-1] = '\n'; break;
1525                 case 'r': p[-1] = '\r'; break;
1526                 case 't': p[-1] = '\t'; break;
1527                 case '\\': p[-1] = '\\'; break;
1528                 case 0: *p = 0; return;
1529                 default: p[-1] = q[-1]; break;
1530             }
1531 }
1532
1533 void
1534 show_bytes(fp, buf, count)
1535      FILE *fp;
1536      char *buf;
1537      int count;
1538 {
1539     while (count--) {
1540         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1541             fprintf(fp, "\\%03o", *buf & 0xff);
1542         } else {
1543             putc(*buf, fp);
1544         }
1545         buf++;
1546     }
1547     fflush(fp);
1548 }
1549
1550 /* Returns an errno value */
1551 int
1552 OutputMaybeTelnet(pr, message, count, outError)
1553      ProcRef pr;
1554      char *message;
1555      int count;
1556      int *outError;
1557 {
1558     char buf[8192], *p, *q, *buflim;
1559     int left, newcount, outcount;
1560
1561     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1562         *appData.gateway != NULLCHAR) {
1563         if (appData.debugMode) {
1564             fprintf(debugFP, ">ICS: ");
1565             show_bytes(debugFP, message, count);
1566             fprintf(debugFP, "\n");
1567         }
1568         return OutputToProcess(pr, message, count, outError);
1569     }
1570
1571     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1572     p = message;
1573     q = buf;
1574     left = count;
1575     newcount = 0;
1576     while (left) {
1577         if (q >= buflim) {
1578             if (appData.debugMode) {
1579                 fprintf(debugFP, ">ICS: ");
1580                 show_bytes(debugFP, buf, newcount);
1581                 fprintf(debugFP, "\n");
1582             }
1583             outcount = OutputToProcess(pr, buf, newcount, outError);
1584             if (outcount < newcount) return -1; /* to be sure */
1585             q = buf;
1586             newcount = 0;
1587         }
1588         if (*p == '\n') {
1589             *q++ = '\r';
1590             newcount++;
1591         } else if (((unsigned char) *p) == TN_IAC) {
1592             *q++ = (char) TN_IAC;
1593             newcount ++;
1594         }
1595         *q++ = *p++;
1596         newcount++;
1597         left--;
1598     }
1599     if (appData.debugMode) {
1600         fprintf(debugFP, ">ICS: ");
1601         show_bytes(debugFP, buf, newcount);
1602         fprintf(debugFP, "\n");
1603     }
1604     outcount = OutputToProcess(pr, buf, newcount, outError);
1605     if (outcount < newcount) return -1; /* to be sure */
1606     return count;
1607 }
1608
1609 void
1610 read_from_player(isr, closure, message, count, error)
1611      InputSourceRef isr;
1612      VOIDSTAR closure;
1613      char *message;
1614      int count;
1615      int error;
1616 {
1617     int outError, outCount;
1618     static int gotEof = 0;
1619
1620     /* Pass data read from player on to ICS */
1621     if (count > 0) {
1622         gotEof = 0;
1623         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1624         if (outCount < count) {
1625             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1626         }
1627     } else if (count < 0) {
1628         RemoveInputSource(isr);
1629         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1630     } else if (gotEof++ > 0) {
1631         RemoveInputSource(isr);
1632         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1633     }
1634 }
1635
1636 void
1637 KeepAlive()
1638 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1639     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1640     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1641     SendToICS("date\n");
1642     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1643 }
1644
1645 /* added routine for printf style output to ics */
1646 void ics_printf(char *format, ...)
1647 {
1648     char buffer[MSG_SIZ];
1649     va_list args;
1650
1651     va_start(args, format);
1652     vsnprintf(buffer, sizeof(buffer), format, args);
1653     buffer[sizeof(buffer)-1] = '\0';
1654     SendToICS(buffer);
1655     va_end(args);
1656 }
1657
1658 void
1659 SendToICS(s)
1660      char *s;
1661 {
1662     int count, outCount, outError;
1663
1664     if (icsPR == NULL) return;
1665
1666     count = strlen(s);
1667     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1668     if (outCount < count) {
1669         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1670     }
1671 }
1672
1673 /* This is used for sending logon scripts to the ICS. Sending
1674    without a delay causes problems when using timestamp on ICC
1675    (at least on my machine). */
1676 void
1677 SendToICSDelayed(s,msdelay)
1678      char *s;
1679      long msdelay;
1680 {
1681     int count, outCount, outError;
1682
1683     if (icsPR == NULL) return;
1684
1685     count = strlen(s);
1686     if (appData.debugMode) {
1687         fprintf(debugFP, ">ICS: ");
1688         show_bytes(debugFP, s, count);
1689         fprintf(debugFP, "\n");
1690     }
1691     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1692                                       msdelay);
1693     if (outCount < count) {
1694         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1695     }
1696 }
1697
1698
1699 /* Remove all highlighting escape sequences in s
1700    Also deletes any suffix starting with '('
1701    */
1702 char *
1703 StripHighlightAndTitle(s)
1704      char *s;
1705 {
1706     static char retbuf[MSG_SIZ];
1707     char *p = retbuf;
1708
1709     while (*s != NULLCHAR) {
1710         while (*s == '\033') {
1711             while (*s != NULLCHAR && !isalpha(*s)) s++;
1712             if (*s != NULLCHAR) s++;
1713         }
1714         while (*s != NULLCHAR && *s != '\033') {
1715             if (*s == '(' || *s == '[') {
1716                 *p = NULLCHAR;
1717                 return retbuf;
1718             }
1719             *p++ = *s++;
1720         }
1721     }
1722     *p = NULLCHAR;
1723     return retbuf;
1724 }
1725
1726 /* Remove all highlighting escape sequences in s */
1727 char *
1728 StripHighlight(s)
1729      char *s;
1730 {
1731     static char retbuf[MSG_SIZ];
1732     char *p = retbuf;
1733
1734     while (*s != NULLCHAR) {
1735         while (*s == '\033') {
1736             while (*s != NULLCHAR && !isalpha(*s)) s++;
1737             if (*s != NULLCHAR) s++;
1738         }
1739         while (*s != NULLCHAR && *s != '\033') {
1740             *p++ = *s++;
1741         }
1742     }
1743     *p = NULLCHAR;
1744     return retbuf;
1745 }
1746
1747 char *variantNames[] = VARIANT_NAMES;
1748 char *
1749 VariantName(v)
1750      VariantClass v;
1751 {
1752     return variantNames[v];
1753 }
1754
1755
1756 /* Identify a variant from the strings the chess servers use or the
1757    PGN Variant tag names we use. */
1758 VariantClass
1759 StringToVariant(e)
1760      char *e;
1761 {
1762     char *p;
1763     int wnum = -1;
1764     VariantClass v = VariantNormal;
1765     int i, found = FALSE;
1766     char buf[MSG_SIZ];
1767     int len;
1768
1769     if (!e) return v;
1770
1771     /* [HGM] skip over optional board-size prefixes */
1772     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1773         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1774         while( *e++ != '_');
1775     }
1776
1777     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1778         v = VariantNormal;
1779         found = TRUE;
1780     } else
1781     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1782       if (StrCaseStr(e, variantNames[i])) {
1783         v = (VariantClass) i;
1784         found = TRUE;
1785         break;
1786       }
1787     }
1788
1789     if (!found) {
1790       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1791           || StrCaseStr(e, "wild/fr")
1792           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1793         v = VariantFischeRandom;
1794       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1795                  (i = 1, p = StrCaseStr(e, "w"))) {
1796         p += i;
1797         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1798         if (isdigit(*p)) {
1799           wnum = atoi(p);
1800         } else {
1801           wnum = -1;
1802         }
1803         switch (wnum) {
1804         case 0: /* FICS only, actually */
1805         case 1:
1806           /* Castling legal even if K starts on d-file */
1807           v = VariantWildCastle;
1808           break;
1809         case 2:
1810         case 3:
1811         case 4:
1812           /* Castling illegal even if K & R happen to start in
1813              normal positions. */
1814           v = VariantNoCastle;
1815           break;
1816         case 5:
1817         case 7:
1818         case 8:
1819         case 10:
1820         case 11:
1821         case 12:
1822         case 13:
1823         case 14:
1824         case 15:
1825         case 18:
1826         case 19:
1827           /* Castling legal iff K & R start in normal positions */
1828           v = VariantNormal;
1829           break;
1830         case 6:
1831         case 20:
1832         case 21:
1833           /* Special wilds for position setup; unclear what to do here */
1834           v = VariantLoadable;
1835           break;
1836         case 9:
1837           /* Bizarre ICC game */
1838           v = VariantTwoKings;
1839           break;
1840         case 16:
1841           v = VariantKriegspiel;
1842           break;
1843         case 17:
1844           v = VariantLosers;
1845           break;
1846         case 22:
1847           v = VariantFischeRandom;
1848           break;
1849         case 23:
1850           v = VariantCrazyhouse;
1851           break;
1852         case 24:
1853           v = VariantBughouse;
1854           break;
1855         case 25:
1856           v = Variant3Check;
1857           break;
1858         case 26:
1859           /* Not quite the same as FICS suicide! */
1860           v = VariantGiveaway;
1861           break;
1862         case 27:
1863           v = VariantAtomic;
1864           break;
1865         case 28:
1866           v = VariantShatranj;
1867           break;
1868
1869         /* Temporary names for future ICC types.  The name *will* change in
1870            the next xboard/WinBoard release after ICC defines it. */
1871         case 29:
1872           v = Variant29;
1873           break;
1874         case 30:
1875           v = Variant30;
1876           break;
1877         case 31:
1878           v = Variant31;
1879           break;
1880         case 32:
1881           v = Variant32;
1882           break;
1883         case 33:
1884           v = Variant33;
1885           break;
1886         case 34:
1887           v = Variant34;
1888           break;
1889         case 35:
1890           v = Variant35;
1891           break;
1892         case 36:
1893           v = Variant36;
1894           break;
1895         case 37:
1896           v = VariantShogi;
1897           break;
1898         case 38:
1899           v = VariantXiangqi;
1900           break;
1901         case 39:
1902           v = VariantCourier;
1903           break;
1904         case 40:
1905           v = VariantGothic;
1906           break;
1907         case 41:
1908           v = VariantCapablanca;
1909           break;
1910         case 42:
1911           v = VariantKnightmate;
1912           break;
1913         case 43:
1914           v = VariantFairy;
1915           break;
1916         case 44:
1917           v = VariantCylinder;
1918           break;
1919         case 45:
1920           v = VariantFalcon;
1921           break;
1922         case 46:
1923           v = VariantCapaRandom;
1924           break;
1925         case 47:
1926           v = VariantBerolina;
1927           break;
1928         case 48:
1929           v = VariantJanus;
1930           break;
1931         case 49:
1932           v = VariantSuper;
1933           break;
1934         case 50:
1935           v = VariantGreat;
1936           break;
1937         case -1:
1938           /* Found "wild" or "w" in the string but no number;
1939              must assume it's normal chess. */
1940           v = VariantNormal;
1941           break;
1942         default:
1943           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1944           if( (len > MSG_SIZ) && appData.debugMode )
1945             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1946
1947           DisplayError(buf, 0);
1948           v = VariantUnknown;
1949           break;
1950         }
1951       }
1952     }
1953     if (appData.debugMode) {
1954       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1955               e, wnum, VariantName(v));
1956     }
1957     return v;
1958 }
1959
1960 static int leftover_start = 0, leftover_len = 0;
1961 char star_match[STAR_MATCH_N][MSG_SIZ];
1962
1963 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1964    advance *index beyond it, and set leftover_start to the new value of
1965    *index; else return FALSE.  If pattern contains the character '*', it
1966    matches any sequence of characters not containing '\r', '\n', or the
1967    character following the '*' (if any), and the matched sequence(s) are
1968    copied into star_match.
1969    */
1970 int
1971 looking_at(buf, index, pattern)
1972      char *buf;
1973      int *index;
1974      char *pattern;
1975 {
1976     char *bufp = &buf[*index], *patternp = pattern;
1977     int star_count = 0;
1978     char *matchp = star_match[0];
1979
1980     for (;;) {
1981         if (*patternp == NULLCHAR) {
1982             *index = leftover_start = bufp - buf;
1983             *matchp = NULLCHAR;
1984             return TRUE;
1985         }
1986         if (*bufp == NULLCHAR) return FALSE;
1987         if (*patternp == '*') {
1988             if (*bufp == *(patternp + 1)) {
1989                 *matchp = NULLCHAR;
1990                 matchp = star_match[++star_count];
1991                 patternp += 2;
1992                 bufp++;
1993                 continue;
1994             } else if (*bufp == '\n' || *bufp == '\r') {
1995                 patternp++;
1996                 if (*patternp == NULLCHAR)
1997                   continue;
1998                 else
1999                   return FALSE;
2000             } else {
2001                 *matchp++ = *bufp++;
2002                 continue;
2003             }
2004         }
2005         if (*patternp != *bufp) return FALSE;
2006         patternp++;
2007         bufp++;
2008     }
2009 }
2010
2011 void
2012 SendToPlayer(data, length)
2013      char *data;
2014      int length;
2015 {
2016     int error, outCount;
2017     outCount = OutputToProcess(NoProc, data, length, &error);
2018     if (outCount < length) {
2019         DisplayFatalError(_("Error writing to display"), error, 1);
2020     }
2021 }
2022
2023 void
2024 PackHolding(packed, holding)
2025      char packed[];
2026      char *holding;
2027 {
2028     char *p = holding;
2029     char *q = packed;
2030     int runlength = 0;
2031     int curr = 9999;
2032     do {
2033         if (*p == curr) {
2034             runlength++;
2035         } else {
2036             switch (runlength) {
2037               case 0:
2038                 break;
2039               case 1:
2040                 *q++ = curr;
2041                 break;
2042               case 2:
2043                 *q++ = curr;
2044                 *q++ = curr;
2045                 break;
2046               default:
2047                 sprintf(q, "%d", runlength);
2048                 while (*q) q++;
2049                 *q++ = curr;
2050                 break;
2051             }
2052             runlength = 1;
2053             curr = *p;
2054         }
2055     } while (*p++);
2056     *q = NULLCHAR;
2057 }
2058
2059 /* Telnet protocol requests from the front end */
2060 void
2061 TelnetRequest(ddww, option)
2062      unsigned char ddww, option;
2063 {
2064     unsigned char msg[3];
2065     int outCount, outError;
2066
2067     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2068
2069     if (appData.debugMode) {
2070         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2071         switch (ddww) {
2072           case TN_DO:
2073             ddwwStr = "DO";
2074             break;
2075           case TN_DONT:
2076             ddwwStr = "DONT";
2077             break;
2078           case TN_WILL:
2079             ddwwStr = "WILL";
2080             break;
2081           case TN_WONT:
2082             ddwwStr = "WONT";
2083             break;
2084           default:
2085             ddwwStr = buf1;
2086             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2087             break;
2088         }
2089         switch (option) {
2090           case TN_ECHO:
2091             optionStr = "ECHO";
2092             break;
2093           default:
2094             optionStr = buf2;
2095             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2096             break;
2097         }
2098         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2099     }
2100     msg[0] = TN_IAC;
2101     msg[1] = ddww;
2102     msg[2] = option;
2103     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2104     if (outCount < 3) {
2105         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2106     }
2107 }
2108
2109 void
2110 DoEcho()
2111 {
2112     if (!appData.icsActive) return;
2113     TelnetRequest(TN_DO, TN_ECHO);
2114 }
2115
2116 void
2117 DontEcho()
2118 {
2119     if (!appData.icsActive) return;
2120     TelnetRequest(TN_DONT, TN_ECHO);
2121 }
2122
2123 void
2124 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2125 {
2126     /* put the holdings sent to us by the server on the board holdings area */
2127     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2128     char p;
2129     ChessSquare piece;
2130
2131     if(gameInfo.holdingsWidth < 2)  return;
2132     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2133         return; // prevent overwriting by pre-board holdings
2134
2135     if( (int)lowestPiece >= BlackPawn ) {
2136         holdingsColumn = 0;
2137         countsColumn = 1;
2138         holdingsStartRow = BOARD_HEIGHT-1;
2139         direction = -1;
2140     } else {
2141         holdingsColumn = BOARD_WIDTH-1;
2142         countsColumn = BOARD_WIDTH-2;
2143         holdingsStartRow = 0;
2144         direction = 1;
2145     }
2146
2147     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2148         board[i][holdingsColumn] = EmptySquare;
2149         board[i][countsColumn]   = (ChessSquare) 0;
2150     }
2151     while( (p=*holdings++) != NULLCHAR ) {
2152         piece = CharToPiece( ToUpper(p) );
2153         if(piece == EmptySquare) continue;
2154         /*j = (int) piece - (int) WhitePawn;*/
2155         j = PieceToNumber(piece);
2156         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2157         if(j < 0) continue;               /* should not happen */
2158         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2159         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2160         board[holdingsStartRow+j*direction][countsColumn]++;
2161     }
2162 }
2163
2164
2165 void
2166 VariantSwitch(Board board, VariantClass newVariant)
2167 {
2168    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2169    static Board oldBoard;
2170
2171    startedFromPositionFile = FALSE;
2172    if(gameInfo.variant == newVariant) return;
2173
2174    /* [HGM] This routine is called each time an assignment is made to
2175     * gameInfo.variant during a game, to make sure the board sizes
2176     * are set to match the new variant. If that means adding or deleting
2177     * holdings, we shift the playing board accordingly
2178     * This kludge is needed because in ICS observe mode, we get boards
2179     * of an ongoing game without knowing the variant, and learn about the
2180     * latter only later. This can be because of the move list we requested,
2181     * in which case the game history is refilled from the beginning anyway,
2182     * but also when receiving holdings of a crazyhouse game. In the latter
2183     * case we want to add those holdings to the already received position.
2184     */
2185
2186
2187    if (appData.debugMode) {
2188      fprintf(debugFP, "Switch board from %s to %s\n",
2189              VariantName(gameInfo.variant), VariantName(newVariant));
2190      setbuf(debugFP, NULL);
2191    }
2192    shuffleOpenings = 0;       /* [HGM] shuffle */
2193    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2194    switch(newVariant)
2195      {
2196      case VariantShogi:
2197        newWidth = 9;  newHeight = 9;
2198        gameInfo.holdingsSize = 7;
2199      case VariantBughouse:
2200      case VariantCrazyhouse:
2201        newHoldingsWidth = 2; break;
2202      case VariantGreat:
2203        newWidth = 10;
2204      case VariantSuper:
2205        newHoldingsWidth = 2;
2206        gameInfo.holdingsSize = 8;
2207        break;
2208      case VariantGothic:
2209      case VariantCapablanca:
2210      case VariantCapaRandom:
2211        newWidth = 10;
2212      default:
2213        newHoldingsWidth = gameInfo.holdingsSize = 0;
2214      };
2215
2216    if(newWidth  != gameInfo.boardWidth  ||
2217       newHeight != gameInfo.boardHeight ||
2218       newHoldingsWidth != gameInfo.holdingsWidth ) {
2219
2220      /* shift position to new playing area, if needed */
2221      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2222        for(i=0; i<BOARD_HEIGHT; i++)
2223          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2224            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2225              board[i][j];
2226        for(i=0; i<newHeight; i++) {
2227          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2228          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2229        }
2230      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2231        for(i=0; i<BOARD_HEIGHT; i++)
2232          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2233            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2234              board[i][j];
2235      }
2236      gameInfo.boardWidth  = newWidth;
2237      gameInfo.boardHeight = newHeight;
2238      gameInfo.holdingsWidth = newHoldingsWidth;
2239      gameInfo.variant = newVariant;
2240      InitDrawingSizes(-2, 0);
2241    } else gameInfo.variant = newVariant;
2242    CopyBoard(oldBoard, board);   // remember correctly formatted board
2243      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2244    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2245 }
2246
2247 static int loggedOn = FALSE;
2248
2249 /*-- Game start info cache: --*/
2250 int gs_gamenum;
2251 char gs_kind[MSG_SIZ];
2252 static char player1Name[128] = "";
2253 static char player2Name[128] = "";
2254 static char cont_seq[] = "\n\\   ";
2255 static int player1Rating = -1;
2256 static int player2Rating = -1;
2257 /*----------------------------*/
2258
2259 ColorClass curColor = ColorNormal;
2260 int suppressKibitz = 0;
2261
2262 // [HGM] seekgraph
2263 Boolean soughtPending = FALSE;
2264 Boolean seekGraphUp;
2265 #define MAX_SEEK_ADS 200
2266 #define SQUARE 0x80
2267 char *seekAdList[MAX_SEEK_ADS];
2268 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2269 float tcList[MAX_SEEK_ADS];
2270 char colorList[MAX_SEEK_ADS];
2271 int nrOfSeekAds = 0;
2272 int minRating = 1010, maxRating = 2800;
2273 int hMargin = 10, vMargin = 20, h, w;
2274 extern int squareSize, lineGap;
2275
2276 void
2277 PlotSeekAd(int i)
2278 {
2279         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2280         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2281         if(r < minRating+100 && r >=0 ) r = minRating+100;
2282         if(r > maxRating) r = maxRating;
2283         if(tc < 1.) tc = 1.;
2284         if(tc > 95.) tc = 95.;
2285         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2286         y = ((double)r - minRating)/(maxRating - minRating)
2287             * (h-vMargin-squareSize/8-1) + vMargin;
2288         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2289         if(strstr(seekAdList[i], " u ")) color = 1;
2290         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2291            !strstr(seekAdList[i], "bullet") &&
2292            !strstr(seekAdList[i], "blitz") &&
2293            !strstr(seekAdList[i], "standard") ) color = 2;
2294         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2295         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2296 }
2297
2298 void
2299 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2300 {
2301         char buf[MSG_SIZ], *ext = "";
2302         VariantClass v = StringToVariant(type);
2303         if(strstr(type, "wild")) {
2304             ext = type + 4; // append wild number
2305             if(v == VariantFischeRandom) type = "chess960"; else
2306             if(v == VariantLoadable) type = "setup"; else
2307             type = VariantName(v);
2308         }
2309         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2310         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2311             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2312             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2313             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2314             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2315             seekNrList[nrOfSeekAds] = nr;
2316             zList[nrOfSeekAds] = 0;
2317             seekAdList[nrOfSeekAds++] = StrSave(buf);
2318             if(plot) PlotSeekAd(nrOfSeekAds-1);
2319         }
2320 }
2321
2322 void
2323 EraseSeekDot(int i)
2324 {
2325     int x = xList[i], y = yList[i], d=squareSize/4, k;
2326     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2327     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2328     // now replot every dot that overlapped
2329     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2330         int xx = xList[k], yy = yList[k];
2331         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2332             DrawSeekDot(xx, yy, colorList[k]);
2333     }
2334 }
2335
2336 void
2337 RemoveSeekAd(int nr)
2338 {
2339         int i;
2340         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2341             EraseSeekDot(i);
2342             if(seekAdList[i]) free(seekAdList[i]);
2343             seekAdList[i] = seekAdList[--nrOfSeekAds];
2344             seekNrList[i] = seekNrList[nrOfSeekAds];
2345             ratingList[i] = ratingList[nrOfSeekAds];
2346             colorList[i]  = colorList[nrOfSeekAds];
2347             tcList[i] = tcList[nrOfSeekAds];
2348             xList[i]  = xList[nrOfSeekAds];
2349             yList[i]  = yList[nrOfSeekAds];
2350             zList[i]  = zList[nrOfSeekAds];
2351             seekAdList[nrOfSeekAds] = NULL;
2352             break;
2353         }
2354 }
2355
2356 Boolean
2357 MatchSoughtLine(char *line)
2358 {
2359     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2360     int nr, base, inc, u=0; char dummy;
2361
2362     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2363        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2364        (u=1) &&
2365        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2366         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2367         // match: compact and save the line
2368         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2369         return TRUE;
2370     }
2371     return FALSE;
2372 }
2373
2374 int
2375 DrawSeekGraph()
2376 {
2377     int i;
2378     if(!seekGraphUp) return FALSE;
2379     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2380     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2381
2382     DrawSeekBackground(0, 0, w, h);
2383     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2384     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2385     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2386         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2387         yy = h-1-yy;
2388         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2389         if(i%500 == 0) {
2390             char buf[MSG_SIZ];
2391             snprintf(buf, MSG_SIZ, "%d", i);
2392             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2393         }
2394     }
2395     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2396     for(i=1; i<100; i+=(i<10?1:5)) {
2397         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2398         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2399         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2400             char buf[MSG_SIZ];
2401             snprintf(buf, MSG_SIZ, "%d", i);
2402             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2403         }
2404     }
2405     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2406     return TRUE;
2407 }
2408
2409 int SeekGraphClick(ClickType click, int x, int y, int moving)
2410 {
2411     static int lastDown = 0, displayed = 0, lastSecond;
2412     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2413         if(click == Release || moving) return FALSE;
2414         nrOfSeekAds = 0;
2415         soughtPending = TRUE;
2416         SendToICS(ics_prefix);
2417         SendToICS("sought\n"); // should this be "sought all"?
2418     } else { // issue challenge based on clicked ad
2419         int dist = 10000; int i, closest = 0, second = 0;
2420         for(i=0; i<nrOfSeekAds; i++) {
2421             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2422             if(d < dist) { dist = d; closest = i; }
2423             second += (d - zList[i] < 120); // count in-range ads
2424             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2425         }
2426         if(dist < 120) {
2427             char buf[MSG_SIZ];
2428             second = (second > 1);
2429             if(displayed != closest || second != lastSecond) {
2430                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2431                 lastSecond = second; displayed = closest;
2432             }
2433             if(click == Press) {
2434                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2435                 lastDown = closest;
2436                 return TRUE;
2437             } // on press 'hit', only show info
2438             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2439             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2440             SendToICS(ics_prefix);
2441             SendToICS(buf);
2442             return TRUE; // let incoming board of started game pop down the graph
2443         } else if(click == Release) { // release 'miss' is ignored
2444             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2445             if(moving == 2) { // right up-click
2446                 nrOfSeekAds = 0; // refresh graph
2447                 soughtPending = TRUE;
2448                 SendToICS(ics_prefix);
2449                 SendToICS("sought\n"); // should this be "sought all"?
2450             }
2451             return TRUE;
2452         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2453         // press miss or release hit 'pop down' seek graph
2454         seekGraphUp = FALSE;
2455         DrawPosition(TRUE, NULL);
2456     }
2457     return TRUE;
2458 }
2459
2460 void
2461 read_from_ics(isr, closure, data, count, error)
2462      InputSourceRef isr;
2463      VOIDSTAR closure;
2464      char *data;
2465      int count;
2466      int error;
2467 {
2468 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2469 #define STARTED_NONE 0
2470 #define STARTED_MOVES 1
2471 #define STARTED_BOARD 2
2472 #define STARTED_OBSERVE 3
2473 #define STARTED_HOLDINGS 4
2474 #define STARTED_CHATTER 5
2475 #define STARTED_COMMENT 6
2476 #define STARTED_MOVES_NOHIDE 7
2477
2478     static int started = STARTED_NONE;
2479     static char parse[20000];
2480     static int parse_pos = 0;
2481     static char buf[BUF_SIZE + 1];
2482     static int firstTime = TRUE, intfSet = FALSE;
2483     static ColorClass prevColor = ColorNormal;
2484     static int savingComment = FALSE;
2485     static int cmatch = 0; // continuation sequence match
2486     char *bp;
2487     char str[MSG_SIZ];
2488     int i, oldi;
2489     int buf_len;
2490     int next_out;
2491     int tkind;
2492     int backup;    /* [DM] For zippy color lines */
2493     char *p;
2494     char talker[MSG_SIZ]; // [HGM] chat
2495     int channel;
2496
2497     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2498
2499     if (appData.debugMode) {
2500       if (!error) {
2501         fprintf(debugFP, "<ICS: ");
2502         show_bytes(debugFP, data, count);
2503         fprintf(debugFP, "\n");
2504       }
2505     }
2506
2507     if (appData.debugMode) { int f = forwardMostMove;
2508         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2509                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2510                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2511     }
2512     if (count > 0) {
2513         /* If last read ended with a partial line that we couldn't parse,
2514            prepend it to the new read and try again. */
2515         if (leftover_len > 0) {
2516             for (i=0; i<leftover_len; i++)
2517               buf[i] = buf[leftover_start + i];
2518         }
2519
2520     /* copy new characters into the buffer */
2521     bp = buf + leftover_len;
2522     buf_len=leftover_len;
2523     for (i=0; i<count; i++)
2524     {
2525         // ignore these
2526         if (data[i] == '\r')
2527             continue;
2528
2529         // join lines split by ICS?
2530         if (!appData.noJoin)
2531         {
2532             /*
2533                 Joining just consists of finding matches against the
2534                 continuation sequence, and discarding that sequence
2535                 if found instead of copying it.  So, until a match
2536                 fails, there's nothing to do since it might be the
2537                 complete sequence, and thus, something we don't want
2538                 copied.
2539             */
2540             if (data[i] == cont_seq[cmatch])
2541             {
2542                 cmatch++;
2543                 if (cmatch == strlen(cont_seq))
2544                 {
2545                     cmatch = 0; // complete match.  just reset the counter
2546
2547                     /*
2548                         it's possible for the ICS to not include the space
2549                         at the end of the last word, making our [correct]
2550                         join operation fuse two separate words.  the server
2551                         does this when the space occurs at the width setting.
2552                     */
2553                     if (!buf_len || buf[buf_len-1] != ' ')
2554                     {
2555                         *bp++ = ' ';
2556                         buf_len++;
2557                     }
2558                 }
2559                 continue;
2560             }
2561             else if (cmatch)
2562             {
2563                 /*
2564                     match failed, so we have to copy what matched before
2565                     falling through and copying this character.  In reality,
2566                     this will only ever be just the newline character, but
2567                     it doesn't hurt to be precise.
2568                 */
2569                 strncpy(bp, cont_seq, cmatch);
2570                 bp += cmatch;
2571                 buf_len += cmatch;
2572                 cmatch = 0;
2573             }
2574         }
2575
2576         // copy this char
2577         *bp++ = data[i];
2578         buf_len++;
2579     }
2580
2581         buf[buf_len] = NULLCHAR;
2582 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2583         next_out = 0;
2584         leftover_start = 0;
2585
2586         i = 0;
2587         while (i < buf_len) {
2588             /* Deal with part of the TELNET option negotiation
2589                protocol.  We refuse to do anything beyond the
2590                defaults, except that we allow the WILL ECHO option,
2591                which ICS uses to turn off password echoing when we are
2592                directly connected to it.  We reject this option
2593                if localLineEditing mode is on (always on in xboard)
2594                and we are talking to port 23, which might be a real
2595                telnet server that will try to keep WILL ECHO on permanently.
2596              */
2597             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2598                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2599                 unsigned char option;
2600                 oldi = i;
2601                 switch ((unsigned char) buf[++i]) {
2602                   case TN_WILL:
2603                     if (appData.debugMode)
2604                       fprintf(debugFP, "\n<WILL ");
2605                     switch (option = (unsigned char) buf[++i]) {
2606                       case TN_ECHO:
2607                         if (appData.debugMode)
2608                           fprintf(debugFP, "ECHO ");
2609                         /* Reply only if this is a change, according
2610                            to the protocol rules. */
2611                         if (remoteEchoOption) break;
2612                         if (appData.localLineEditing &&
2613                             atoi(appData.icsPort) == TN_PORT) {
2614                             TelnetRequest(TN_DONT, TN_ECHO);
2615                         } else {
2616                             EchoOff();
2617                             TelnetRequest(TN_DO, TN_ECHO);
2618                             remoteEchoOption = TRUE;
2619                         }
2620                         break;
2621                       default:
2622                         if (appData.debugMode)
2623                           fprintf(debugFP, "%d ", option);
2624                         /* Whatever this is, we don't want it. */
2625                         TelnetRequest(TN_DONT, option);
2626                         break;
2627                     }
2628                     break;
2629                   case TN_WONT:
2630                     if (appData.debugMode)
2631                       fprintf(debugFP, "\n<WONT ");
2632                     switch (option = (unsigned char) buf[++i]) {
2633                       case TN_ECHO:
2634                         if (appData.debugMode)
2635                           fprintf(debugFP, "ECHO ");
2636                         /* Reply only if this is a change, according
2637                            to the protocol rules. */
2638                         if (!remoteEchoOption) break;
2639                         EchoOn();
2640                         TelnetRequest(TN_DONT, TN_ECHO);
2641                         remoteEchoOption = FALSE;
2642                         break;
2643                       default:
2644                         if (appData.debugMode)
2645                           fprintf(debugFP, "%d ", (unsigned char) option);
2646                         /* Whatever this is, it must already be turned
2647                            off, because we never agree to turn on
2648                            anything non-default, so according to the
2649                            protocol rules, we don't reply. */
2650                         break;
2651                     }
2652                     break;
2653                   case TN_DO:
2654                     if (appData.debugMode)
2655                       fprintf(debugFP, "\n<DO ");
2656                     switch (option = (unsigned char) buf[++i]) {
2657                       default:
2658                         /* Whatever this is, we refuse to do it. */
2659                         if (appData.debugMode)
2660                           fprintf(debugFP, "%d ", option);
2661                         TelnetRequest(TN_WONT, option);
2662                         break;
2663                     }
2664                     break;
2665                   case TN_DONT:
2666                     if (appData.debugMode)
2667                       fprintf(debugFP, "\n<DONT ");
2668                     switch (option = (unsigned char) buf[++i]) {
2669                       default:
2670                         if (appData.debugMode)
2671                           fprintf(debugFP, "%d ", option);
2672                         /* Whatever this is, we are already not doing
2673                            it, because we never agree to do anything
2674                            non-default, so according to the protocol
2675                            rules, we don't reply. */
2676                         break;
2677                     }
2678                     break;
2679                   case TN_IAC:
2680                     if (appData.debugMode)
2681                       fprintf(debugFP, "\n<IAC ");
2682                     /* Doubled IAC; pass it through */
2683                     i--;
2684                     break;
2685                   default:
2686                     if (appData.debugMode)
2687                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2688                     /* Drop all other telnet commands on the floor */
2689                     break;
2690                 }
2691                 if (oldi > next_out)
2692                   SendToPlayer(&buf[next_out], oldi - next_out);
2693                 if (++i > next_out)
2694                   next_out = i;
2695                 continue;
2696             }
2697
2698             /* OK, this at least will *usually* work */
2699             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2700                 loggedOn = TRUE;
2701             }
2702
2703             if (loggedOn && !intfSet) {
2704                 if (ics_type == ICS_ICC) {
2705                   snprintf(str, MSG_SIZ,
2706                           "/set-quietly interface %s\n/set-quietly style 12\n",
2707                           programVersion);
2708                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2709                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2710                 } else if (ics_type == ICS_CHESSNET) {
2711                   snprintf(str, MSG_SIZ, "/style 12\n");
2712                 } else {
2713                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2714                   strcat(str, programVersion);
2715                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2716                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2717                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2718 #ifdef WIN32
2719                   strcat(str, "$iset nohighlight 1\n");
2720 #endif
2721                   strcat(str, "$iset lock 1\n$style 12\n");
2722                 }
2723                 SendToICS(str);
2724                 NotifyFrontendLogin();
2725                 intfSet = TRUE;
2726             }
2727
2728             if (started == STARTED_COMMENT) {
2729                 /* Accumulate characters in comment */
2730                 parse[parse_pos++] = buf[i];
2731                 if (buf[i] == '\n') {
2732                     parse[parse_pos] = NULLCHAR;
2733                     if(chattingPartner>=0) {
2734                         char mess[MSG_SIZ];
2735                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2736                         OutputChatMessage(chattingPartner, mess);
2737                         chattingPartner = -1;
2738                         next_out = i+1; // [HGM] suppress printing in ICS window
2739                     } else
2740                     if(!suppressKibitz) // [HGM] kibitz
2741                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2742                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2743                         int nrDigit = 0, nrAlph = 0, j;
2744                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2745                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2746                         parse[parse_pos] = NULLCHAR;
2747                         // try to be smart: if it does not look like search info, it should go to
2748                         // ICS interaction window after all, not to engine-output window.
2749                         for(j=0; j<parse_pos; j++) { // count letters and digits
2750                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2751                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2752                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2753                         }
2754                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2755                             int depth=0; float score;
2756                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2757                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2758                                 pvInfoList[forwardMostMove-1].depth = depth;
2759                                 pvInfoList[forwardMostMove-1].score = 100*score;
2760                             }
2761                             OutputKibitz(suppressKibitz, parse);
2762                         } else {
2763                             char tmp[MSG_SIZ];
2764                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2765                             SendToPlayer(tmp, strlen(tmp));
2766                         }
2767                         next_out = i+1; // [HGM] suppress printing in ICS window
2768                     }
2769                     started = STARTED_NONE;
2770                 } else {
2771                     /* Don't match patterns against characters in comment */
2772                     i++;
2773                     continue;
2774                 }
2775             }
2776             if (started == STARTED_CHATTER) {
2777                 if (buf[i] != '\n') {
2778                     /* Don't match patterns against characters in chatter */
2779                     i++;
2780                     continue;
2781                 }
2782                 started = STARTED_NONE;
2783                 if(suppressKibitz) next_out = i+1;
2784             }
2785
2786             /* Kludge to deal with rcmd protocol */
2787             if (firstTime && looking_at(buf, &i, "\001*")) {
2788                 DisplayFatalError(&buf[1], 0, 1);
2789                 continue;
2790             } else {
2791                 firstTime = FALSE;
2792             }
2793
2794             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2795                 ics_type = ICS_ICC;
2796                 ics_prefix = "/";
2797                 if (appData.debugMode)
2798                   fprintf(debugFP, "ics_type %d\n", ics_type);
2799                 continue;
2800             }
2801             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2802                 ics_type = ICS_FICS;
2803                 ics_prefix = "$";
2804                 if (appData.debugMode)
2805                   fprintf(debugFP, "ics_type %d\n", ics_type);
2806                 continue;
2807             }
2808             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2809                 ics_type = ICS_CHESSNET;
2810                 ics_prefix = "/";
2811                 if (appData.debugMode)
2812                   fprintf(debugFP, "ics_type %d\n", ics_type);
2813                 continue;
2814             }
2815
2816             if (!loggedOn &&
2817                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2818                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2819                  looking_at(buf, &i, "will be \"*\""))) {
2820               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2821               continue;
2822             }
2823
2824             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2825               char buf[MSG_SIZ];
2826               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2827               DisplayIcsInteractionTitle(buf);
2828               have_set_title = TRUE;
2829             }
2830
2831             /* skip finger notes */
2832             if (started == STARTED_NONE &&
2833                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2834                  (buf[i] == '1' && buf[i+1] == '0')) &&
2835                 buf[i+2] == ':' && buf[i+3] == ' ') {
2836               started = STARTED_CHATTER;
2837               i += 3;
2838               continue;
2839             }
2840
2841             oldi = i;
2842             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2843             if(appData.seekGraph) {
2844                 if(soughtPending && MatchSoughtLine(buf+i)) {
2845                     i = strstr(buf+i, "rated") - buf;
2846                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2847                     next_out = leftover_start = i;
2848                     started = STARTED_CHATTER;
2849                     suppressKibitz = TRUE;
2850                     continue;
2851                 }
2852                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2853                         && looking_at(buf, &i, "* ads displayed")) {
2854                     soughtPending = FALSE;
2855                     seekGraphUp = TRUE;
2856                     DrawSeekGraph();
2857                     continue;
2858                 }
2859                 if(appData.autoRefresh) {
2860                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2861                         int s = (ics_type == ICS_ICC); // ICC format differs
2862                         if(seekGraphUp)
2863                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2864                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2865                         looking_at(buf, &i, "*% "); // eat prompt
2866                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2867                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2868                         next_out = i; // suppress
2869                         continue;
2870                     }
2871                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2872                         char *p = star_match[0];
2873                         while(*p) {
2874                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2875                             while(*p && *p++ != ' '); // next
2876                         }
2877                         looking_at(buf, &i, "*% "); // eat prompt
2878                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2879                         next_out = i;
2880                         continue;
2881                     }
2882                 }
2883             }
2884
2885             /* skip formula vars */
2886             if (started == STARTED_NONE &&
2887                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2888               started = STARTED_CHATTER;
2889               i += 3;
2890               continue;
2891             }
2892
2893             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2894             if (appData.autoKibitz && started == STARTED_NONE &&
2895                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2896                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2897                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2898                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2899                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2900                         suppressKibitz = TRUE;
2901                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2902                         next_out = i;
2903                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2904                                 && (gameMode == IcsPlayingWhite)) ||
2905                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2906                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2907                             started = STARTED_CHATTER; // own kibitz we simply discard
2908                         else {
2909                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2910                             parse_pos = 0; parse[0] = NULLCHAR;
2911                             savingComment = TRUE;
2912                             suppressKibitz = gameMode != IcsObserving ? 2 :
2913                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2914                         }
2915                         continue;
2916                 } else
2917                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2918                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2919                          && atoi(star_match[0])) {
2920                     // suppress the acknowledgements of our own autoKibitz
2921                     char *p;
2922                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2923                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2924                     SendToPlayer(star_match[0], strlen(star_match[0]));
2925                     if(looking_at(buf, &i, "*% ")) // eat prompt
2926                         suppressKibitz = FALSE;
2927                     next_out = i;
2928                     continue;
2929                 }
2930             } // [HGM] kibitz: end of patch
2931
2932             // [HGM] chat: intercept tells by users for which we have an open chat window
2933             channel = -1;
2934             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2935                                            looking_at(buf, &i, "* whispers:") ||
2936                                            looking_at(buf, &i, "* kibitzes:") ||
2937                                            looking_at(buf, &i, "* shouts:") ||
2938                                            looking_at(buf, &i, "* c-shouts:") ||
2939                                            looking_at(buf, &i, "--> * ") ||
2940                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2941                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2942                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2943                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2944                 int p;
2945                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2946                 chattingPartner = -1;
2947
2948                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2949                 for(p=0; p<MAX_CHAT; p++) {
2950                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2951                     talker[0] = '['; strcat(talker, "] ");
2952                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2953                     chattingPartner = p; break;
2954                     }
2955                 } else
2956                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2957                 for(p=0; p<MAX_CHAT; p++) {
2958                     if(!strcmp("kibitzes", chatPartner[p])) {
2959                         talker[0] = '['; strcat(talker, "] ");
2960                         chattingPartner = p; break;
2961                     }
2962                 } else
2963                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2964                 for(p=0; p<MAX_CHAT; p++) {
2965                     if(!strcmp("whispers", chatPartner[p])) {
2966                         talker[0] = '['; strcat(talker, "] ");
2967                         chattingPartner = p; break;
2968                     }
2969                 } else
2970                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2971                   if(buf[i-8] == '-' && buf[i-3] == 't')
2972                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2973                     if(!strcmp("c-shouts", chatPartner[p])) {
2974                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2975                         chattingPartner = p; break;
2976                     }
2977                   }
2978                   if(chattingPartner < 0)
2979                   for(p=0; p<MAX_CHAT; p++) {
2980                     if(!strcmp("shouts", chatPartner[p])) {
2981                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2982                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2983                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2984                         chattingPartner = p; break;
2985                     }
2986                   }
2987                 }
2988                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2989                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2990                     talker[0] = 0; Colorize(ColorTell, FALSE);
2991                     chattingPartner = p; break;
2992                 }
2993                 if(chattingPartner<0) i = oldi; else {
2994                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2995                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2996                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2997                     started = STARTED_COMMENT;
2998                     parse_pos = 0; parse[0] = NULLCHAR;
2999                     savingComment = 3 + chattingPartner; // counts as TRUE
3000                     suppressKibitz = TRUE;
3001                     continue;
3002                 }
3003             } // [HGM] chat: end of patch
3004
3005           backup = i;
3006             if (appData.zippyTalk || appData.zippyPlay) {
3007                 /* [DM] Backup address for color zippy lines */
3008 #if ZIPPY
3009                if (loggedOn == TRUE)
3010                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3011                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3012 #endif
3013             } // [DM] 'else { ' deleted
3014                 if (
3015                     /* Regular tells and says */
3016                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3017                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3018                     looking_at(buf, &i, "* says: ") ||
3019                     /* Don't color "message" or "messages" output */
3020                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3021                     looking_at(buf, &i, "*. * at *:*: ") ||
3022                     looking_at(buf, &i, "--* (*:*): ") ||
3023                     /* Message notifications (same color as tells) */
3024                     looking_at(buf, &i, "* has left a message ") ||
3025                     looking_at(buf, &i, "* just sent you a message:\n") ||
3026                     /* Whispers and kibitzes */
3027                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3028                     looking_at(buf, &i, "* kibitzes: ") ||
3029                     /* Channel tells */
3030                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3031
3032                   if (tkind == 1 && strchr(star_match[0], ':')) {
3033                       /* Avoid "tells you:" spoofs in channels */
3034                      tkind = 3;
3035                   }
3036                   if (star_match[0][0] == NULLCHAR ||
3037                       strchr(star_match[0], ' ') ||
3038                       (tkind == 3 && strchr(star_match[1], ' '))) {
3039                     /* Reject bogus matches */
3040                     i = oldi;
3041                   } else {
3042                     if (appData.colorize) {
3043                       if (oldi > next_out) {
3044                         SendToPlayer(&buf[next_out], oldi - next_out);
3045                         next_out = oldi;
3046                       }
3047                       switch (tkind) {
3048                       case 1:
3049                         Colorize(ColorTell, FALSE);
3050                         curColor = ColorTell;
3051                         break;
3052                       case 2:
3053                         Colorize(ColorKibitz, FALSE);
3054                         curColor = ColorKibitz;
3055                         break;
3056                       case 3:
3057                         p = strrchr(star_match[1], '(');
3058                         if (p == NULL) {
3059                           p = star_match[1];
3060                         } else {
3061                           p++;
3062                         }
3063                         if (atoi(p) == 1) {
3064                           Colorize(ColorChannel1, FALSE);
3065                           curColor = ColorChannel1;
3066                         } else {
3067                           Colorize(ColorChannel, FALSE);
3068                           curColor = ColorChannel;
3069                         }
3070                         break;
3071                       case 5:
3072                         curColor = ColorNormal;
3073                         break;
3074                       }
3075                     }
3076                     if (started == STARTED_NONE && appData.autoComment &&
3077                         (gameMode == IcsObserving ||
3078                          gameMode == IcsPlayingWhite ||
3079                          gameMode == IcsPlayingBlack)) {
3080                       parse_pos = i - oldi;
3081                       memcpy(parse, &buf[oldi], parse_pos);
3082                       parse[parse_pos] = NULLCHAR;
3083                       started = STARTED_COMMENT;
3084                       savingComment = TRUE;
3085                     } else {
3086                       started = STARTED_CHATTER;
3087                       savingComment = FALSE;
3088                     }
3089                     loggedOn = TRUE;
3090                     continue;
3091                   }
3092                 }
3093
3094                 if (looking_at(buf, &i, "* s-shouts: ") ||
3095                     looking_at(buf, &i, "* c-shouts: ")) {
3096                     if (appData.colorize) {
3097                         if (oldi > next_out) {
3098                             SendToPlayer(&buf[next_out], oldi - next_out);
3099                             next_out = oldi;
3100                         }
3101                         Colorize(ColorSShout, FALSE);
3102                         curColor = ColorSShout;
3103                     }
3104                     loggedOn = TRUE;
3105                     started = STARTED_CHATTER;
3106                     continue;
3107                 }
3108
3109                 if (looking_at(buf, &i, "--->")) {
3110                     loggedOn = TRUE;
3111                     continue;
3112                 }
3113
3114                 if (looking_at(buf, &i, "* shouts: ") ||
3115                     looking_at(buf, &i, "--> ")) {
3116                     if (appData.colorize) {
3117                         if (oldi > next_out) {
3118                             SendToPlayer(&buf[next_out], oldi - next_out);
3119                             next_out = oldi;
3120                         }
3121                         Colorize(ColorShout, FALSE);
3122                         curColor = ColorShout;
3123                     }
3124                     loggedOn = TRUE;
3125                     started = STARTED_CHATTER;
3126                     continue;
3127                 }
3128
3129                 if (looking_at( buf, &i, "Challenge:")) {
3130                     if (appData.colorize) {
3131                         if (oldi > next_out) {
3132                             SendToPlayer(&buf[next_out], oldi - next_out);
3133                             next_out = oldi;
3134                         }
3135                         Colorize(ColorChallenge, FALSE);
3136                         curColor = ColorChallenge;
3137                     }
3138                     loggedOn = TRUE;
3139                     continue;
3140                 }
3141
3142                 if (looking_at(buf, &i, "* offers you") ||
3143                     looking_at(buf, &i, "* offers to be") ||
3144                     looking_at(buf, &i, "* would like to") ||
3145                     looking_at(buf, &i, "* requests to") ||
3146                     looking_at(buf, &i, "Your opponent offers") ||
3147                     looking_at(buf, &i, "Your opponent requests")) {
3148
3149                     if (appData.colorize) {
3150                         if (oldi > next_out) {
3151                             SendToPlayer(&buf[next_out], oldi - next_out);
3152                             next_out = oldi;
3153                         }
3154                         Colorize(ColorRequest, FALSE);
3155                         curColor = ColorRequest;
3156                     }
3157                     continue;
3158                 }
3159
3160                 if (looking_at(buf, &i, "* (*) seeking")) {
3161                     if (appData.colorize) {
3162                         if (oldi > next_out) {
3163                             SendToPlayer(&buf[next_out], oldi - next_out);
3164                             next_out = oldi;
3165                         }
3166                         Colorize(ColorSeek, FALSE);
3167                         curColor = ColorSeek;
3168                     }
3169                     continue;
3170             }
3171
3172           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3173
3174             if (looking_at(buf, &i, "\\   ")) {
3175                 if (prevColor != ColorNormal) {
3176                     if (oldi > next_out) {
3177                         SendToPlayer(&buf[next_out], oldi - next_out);
3178                         next_out = oldi;
3179                     }
3180                     Colorize(prevColor, TRUE);
3181                     curColor = prevColor;
3182                 }
3183                 if (savingComment) {
3184                     parse_pos = i - oldi;
3185                     memcpy(parse, &buf[oldi], parse_pos);
3186                     parse[parse_pos] = NULLCHAR;
3187                     started = STARTED_COMMENT;
3188                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3189                         chattingPartner = savingComment - 3; // kludge to remember the box
3190                 } else {
3191                     started = STARTED_CHATTER;
3192                 }
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "Black Strength :") ||
3197                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3198                 looking_at(buf, &i, "<10>") ||
3199                 looking_at(buf, &i, "#@#")) {
3200                 /* Wrong board style */
3201                 loggedOn = TRUE;
3202                 SendToICS(ics_prefix);
3203                 SendToICS("set style 12\n");
3204                 SendToICS(ics_prefix);
3205                 SendToICS("refresh\n");
3206                 continue;
3207             }
3208
3209             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3210                 ICSInitScript();
3211                 have_sent_ICS_logon = 1;
3212                 continue;
3213             }
3214
3215             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3216                 (looking_at(buf, &i, "\n<12> ") ||
3217                  looking_at(buf, &i, "<12> "))) {
3218                 loggedOn = TRUE;
3219                 if (oldi > next_out) {
3220                     SendToPlayer(&buf[next_out], oldi - next_out);
3221                 }
3222                 next_out = i;
3223                 started = STARTED_BOARD;
3224                 parse_pos = 0;
3225                 continue;
3226             }
3227
3228             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3229                 looking_at(buf, &i, "<b1> ")) {
3230                 if (oldi > next_out) {
3231                     SendToPlayer(&buf[next_out], oldi - next_out);
3232                 }
3233                 next_out = i;
3234                 started = STARTED_HOLDINGS;
3235                 parse_pos = 0;
3236                 continue;
3237             }
3238
3239             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3240                 loggedOn = TRUE;
3241                 /* Header for a move list -- first line */
3242
3243                 switch (ics_getting_history) {
3244                   case H_FALSE:
3245                     switch (gameMode) {
3246                       case IcsIdle:
3247                       case BeginningOfGame:
3248                         /* User typed "moves" or "oldmoves" while we
3249                            were idle.  Pretend we asked for these
3250                            moves and soak them up so user can step
3251                            through them and/or save them.
3252                            */
3253                         Reset(FALSE, TRUE);
3254                         gameMode = IcsObserving;
3255                         ModeHighlight();
3256                         ics_gamenum = -1;
3257                         ics_getting_history = H_GOT_UNREQ_HEADER;
3258                         break;
3259                       case EditGame: /*?*/
3260                       case EditPosition: /*?*/
3261                         /* Should above feature work in these modes too? */
3262                         /* For now it doesn't */
3263                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3264                         break;
3265                       default:
3266                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3267                         break;
3268                     }
3269                     break;
3270                   case H_REQUESTED:
3271                     /* Is this the right one? */
3272                     if (gameInfo.white && gameInfo.black &&
3273                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3274                         strcmp(gameInfo.black, star_match[2]) == 0) {
3275                         /* All is well */
3276                         ics_getting_history = H_GOT_REQ_HEADER;
3277                     }
3278                     break;
3279                   case H_GOT_REQ_HEADER:
3280                   case H_GOT_UNREQ_HEADER:
3281                   case H_GOT_UNWANTED_HEADER:
3282                   case H_GETTING_MOVES:
3283                     /* Should not happen */
3284                     DisplayError(_("Error gathering move list: two headers"), 0);
3285                     ics_getting_history = H_FALSE;
3286                     break;
3287                 }
3288
3289                 /* Save player ratings into gameInfo if needed */
3290                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3291                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3292                     (gameInfo.whiteRating == -1 ||
3293                      gameInfo.blackRating == -1)) {
3294
3295                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3296                     gameInfo.blackRating = string_to_rating(star_match[3]);
3297                     if (appData.debugMode)
3298                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3299                               gameInfo.whiteRating, gameInfo.blackRating);
3300                 }
3301                 continue;
3302             }
3303
3304             if (looking_at(buf, &i,
3305               "* * match, initial time: * minute*, increment: * second")) {
3306                 /* Header for a move list -- second line */
3307                 /* Initial board will follow if this is a wild game */
3308                 if (gameInfo.event != NULL) free(gameInfo.event);
3309                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3310                 gameInfo.event = StrSave(str);
3311                 /* [HGM] we switched variant. Translate boards if needed. */
3312                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3313                 continue;
3314             }
3315
3316             if (looking_at(buf, &i, "Move  ")) {
3317                 /* Beginning of a move list */
3318                 switch (ics_getting_history) {
3319                   case H_FALSE:
3320                     /* Normally should not happen */
3321                     /* Maybe user hit reset while we were parsing */
3322                     break;
3323                   case H_REQUESTED:
3324                     /* Happens if we are ignoring a move list that is not
3325                      * the one we just requested.  Common if the user
3326                      * tries to observe two games without turning off
3327                      * getMoveList */
3328                     break;
3329                   case H_GETTING_MOVES:
3330                     /* Should not happen */
3331                     DisplayError(_("Error gathering move list: nested"), 0);
3332                     ics_getting_history = H_FALSE;
3333                     break;
3334                   case H_GOT_REQ_HEADER:
3335                     ics_getting_history = H_GETTING_MOVES;
3336                     started = STARTED_MOVES;
3337                     parse_pos = 0;
3338                     if (oldi > next_out) {
3339                         SendToPlayer(&buf[next_out], oldi - next_out);
3340                     }
3341                     break;
3342                   case H_GOT_UNREQ_HEADER:
3343                     ics_getting_history = H_GETTING_MOVES;
3344                     started = STARTED_MOVES_NOHIDE;
3345                     parse_pos = 0;
3346                     break;
3347                   case H_GOT_UNWANTED_HEADER:
3348                     ics_getting_history = H_FALSE;
3349                     break;
3350                 }
3351                 continue;
3352             }
3353
3354             if (looking_at(buf, &i, "% ") ||
3355                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3356                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3357                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3358                     soughtPending = FALSE;
3359                     seekGraphUp = TRUE;
3360                     DrawSeekGraph();
3361                 }
3362                 if(suppressKibitz) next_out = i;
3363                 savingComment = FALSE;
3364                 suppressKibitz = 0;
3365                 switch (started) {
3366                   case STARTED_MOVES:
3367                   case STARTED_MOVES_NOHIDE:
3368                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3369                     parse[parse_pos + i - oldi] = NULLCHAR;
3370                     ParseGameHistory(parse);
3371 #if ZIPPY
3372                     if (appData.zippyPlay && first.initDone) {
3373                         FeedMovesToProgram(&first, forwardMostMove);
3374                         if (gameMode == IcsPlayingWhite) {
3375                             if (WhiteOnMove(forwardMostMove)) {
3376                                 if (first.sendTime) {
3377                                   if (first.useColors) {
3378                                     SendToProgram("black\n", &first);
3379                                   }
3380                                   SendTimeRemaining(&first, TRUE);
3381                                 }
3382                                 if (first.useColors) {
3383                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3384                                 }
3385                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3386                                 first.maybeThinking = TRUE;
3387                             } else {
3388                                 if (first.usePlayother) {
3389                                   if (first.sendTime) {
3390                                     SendTimeRemaining(&first, TRUE);
3391                                   }
3392                                   SendToProgram("playother\n", &first);
3393                                   firstMove = FALSE;
3394                                 } else {
3395                                   firstMove = TRUE;
3396                                 }
3397                             }
3398                         } else if (gameMode == IcsPlayingBlack) {
3399                             if (!WhiteOnMove(forwardMostMove)) {
3400                                 if (first.sendTime) {
3401                                   if (first.useColors) {
3402                                     SendToProgram("white\n", &first);
3403                                   }
3404                                   SendTimeRemaining(&first, FALSE);
3405                                 }
3406                                 if (first.useColors) {
3407                                   SendToProgram("black\n", &first);
3408                                 }
3409                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3410                                 first.maybeThinking = TRUE;
3411                             } else {
3412                                 if (first.usePlayother) {
3413                                   if (first.sendTime) {
3414                                     SendTimeRemaining(&first, FALSE);
3415                                   }
3416                                   SendToProgram("playother\n", &first);
3417                                   firstMove = FALSE;
3418                                 } else {
3419                                   firstMove = TRUE;
3420                                 }
3421                             }
3422                         }
3423                     }
3424 #endif
3425                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3426                         /* Moves came from oldmoves or moves command
3427                            while we weren't doing anything else.
3428                            */
3429                         currentMove = forwardMostMove;
3430                         ClearHighlights();/*!!could figure this out*/
3431                         flipView = appData.flipView;
3432                         DrawPosition(TRUE, boards[currentMove]);
3433                         DisplayBothClocks();
3434                         snprintf(str, MSG_SIZ, "%s vs. %s",
3435                                 gameInfo.white, gameInfo.black);
3436                         DisplayTitle(str);
3437                         gameMode = IcsIdle;
3438                     } else {
3439                         /* Moves were history of an active game */
3440                         if (gameInfo.resultDetails != NULL) {
3441                             free(gameInfo.resultDetails);
3442                             gameInfo.resultDetails = NULL;
3443                         }
3444                     }
3445                     HistorySet(parseList, backwardMostMove,
3446                                forwardMostMove, currentMove-1);
3447                     DisplayMove(currentMove - 1);
3448                     if (started == STARTED_MOVES) next_out = i;
3449                     started = STARTED_NONE;
3450                     ics_getting_history = H_FALSE;
3451                     break;
3452
3453                   case STARTED_OBSERVE:
3454                     started = STARTED_NONE;
3455                     SendToICS(ics_prefix);
3456                     SendToICS("refresh\n");
3457                     break;
3458
3459                   default:
3460                     break;
3461                 }
3462                 if(bookHit) { // [HGM] book: simulate book reply
3463                     static char bookMove[MSG_SIZ]; // a bit generous?
3464
3465                     programStats.nodes = programStats.depth = programStats.time =
3466                     programStats.score = programStats.got_only_move = 0;
3467                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3468
3469                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3470                     strcat(bookMove, bookHit);
3471                     HandleMachineMove(bookMove, &first);
3472                 }
3473                 continue;
3474             }
3475
3476             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3477                  started == STARTED_HOLDINGS ||
3478                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3479                 /* Accumulate characters in move list or board */
3480                 parse[parse_pos++] = buf[i];
3481             }
3482
3483             /* Start of game messages.  Mostly we detect start of game
3484                when the first board image arrives.  On some versions
3485                of the ICS, though, we need to do a "refresh" after starting
3486                to observe in order to get the current board right away. */
3487             if (looking_at(buf, &i, "Adding game * to observation list")) {
3488                 started = STARTED_OBSERVE;
3489                 continue;
3490             }
3491
3492             /* Handle auto-observe */
3493             if (appData.autoObserve &&
3494                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3495                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3496                 char *player;
3497                 /* Choose the player that was highlighted, if any. */
3498                 if (star_match[0][0] == '\033' ||
3499                     star_match[1][0] != '\033') {
3500                     player = star_match[0];
3501                 } else {
3502                     player = star_match[2];
3503                 }
3504                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3505                         ics_prefix, StripHighlightAndTitle(player));
3506                 SendToICS(str);
3507
3508                 /* Save ratings from notify string */
3509                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3510                 player1Rating = string_to_rating(star_match[1]);
3511                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3512                 player2Rating = string_to_rating(star_match[3]);
3513
3514                 if (appData.debugMode)
3515                   fprintf(debugFP,
3516                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3517                           player1Name, player1Rating,
3518                           player2Name, player2Rating);
3519
3520                 continue;
3521             }
3522
3523             /* Deal with automatic examine mode after a game,
3524                and with IcsObserving -> IcsExamining transition */
3525             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3526                 looking_at(buf, &i, "has made you an examiner of game *")) {
3527
3528                 int gamenum = atoi(star_match[0]);
3529                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3530                     gamenum == ics_gamenum) {
3531                     /* We were already playing or observing this game;
3532                        no need to refetch history */
3533                     gameMode = IcsExamining;
3534                     if (pausing) {
3535                         pauseExamForwardMostMove = forwardMostMove;
3536                     } else if (currentMove < forwardMostMove) {
3537                         ForwardInner(forwardMostMove);
3538                     }
3539                 } else {
3540                     /* I don't think this case really can happen */
3541                     SendToICS(ics_prefix);
3542                     SendToICS("refresh\n");
3543                 }
3544                 continue;
3545             }
3546
3547             /* Error messages */
3548 //          if (ics_user_moved) {
3549             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3550                 if (looking_at(buf, &i, "Illegal move") ||
3551                     looking_at(buf, &i, "Not a legal move") ||
3552                     looking_at(buf, &i, "Your king is in check") ||
3553                     looking_at(buf, &i, "It isn't your turn") ||
3554                     looking_at(buf, &i, "It is not your move")) {
3555                     /* Illegal move */
3556                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3557                         currentMove = forwardMostMove-1;
3558                         DisplayMove(currentMove - 1); /* before DMError */
3559                         DrawPosition(FALSE, boards[currentMove]);
3560                         SwitchClocks(forwardMostMove-1); // [HGM] race
3561                         DisplayBothClocks();
3562                     }
3563                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3564                     ics_user_moved = 0;
3565                     continue;
3566                 }
3567             }
3568
3569             if (looking_at(buf, &i, "still have time") ||
3570                 looking_at(buf, &i, "not out of time") ||
3571                 looking_at(buf, &i, "either player is out of time") ||
3572                 looking_at(buf, &i, "has timeseal; checking")) {
3573                 /* We must have called his flag a little too soon */
3574                 whiteFlag = blackFlag = FALSE;
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i, "added * seconds to") ||
3579                 looking_at(buf, &i, "seconds were added to")) {
3580                 /* Update the clocks */
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3587                 ics_clock_paused = TRUE;
3588                 StopClocks();
3589                 continue;
3590             }
3591
3592             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3593                 ics_clock_paused = FALSE;
3594                 StartClocks();
3595                 continue;
3596             }
3597
3598             /* Grab player ratings from the Creating: message.
3599                Note we have to check for the special case when
3600                the ICS inserts things like [white] or [black]. */
3601             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3602                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3603                 /* star_matches:
3604                    0    player 1 name (not necessarily white)
3605                    1    player 1 rating
3606                    2    empty, white, or black (IGNORED)
3607                    3    player 2 name (not necessarily black)
3608                    4    player 2 rating
3609
3610                    The names/ratings are sorted out when the game
3611                    actually starts (below).
3612                 */
3613                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3614                 player1Rating = string_to_rating(star_match[1]);
3615                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3616                 player2Rating = string_to_rating(star_match[4]);
3617
3618                 if (appData.debugMode)
3619                   fprintf(debugFP,
3620                           "Ratings from 'Creating:' %s %d, %s %d\n",
3621                           player1Name, player1Rating,
3622                           player2Name, player2Rating);
3623
3624                 continue;
3625             }
3626
3627             /* Improved generic start/end-of-game messages */
3628             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3629                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3630                 /* If tkind == 0: */
3631                 /* star_match[0] is the game number */
3632                 /*           [1] is the white player's name */
3633                 /*           [2] is the black player's name */
3634                 /* For end-of-game: */
3635                 /*           [3] is the reason for the game end */
3636                 /*           [4] is a PGN end game-token, preceded by " " */
3637                 /* For start-of-game: */
3638                 /*           [3] begins with "Creating" or "Continuing" */
3639                 /*           [4] is " *" or empty (don't care). */
3640                 int gamenum = atoi(star_match[0]);
3641                 char *whitename, *blackname, *why, *endtoken;
3642                 ChessMove endtype = EndOfFile;
3643
3644                 if (tkind == 0) {
3645                   whitename = star_match[1];
3646                   blackname = star_match[2];
3647                   why = star_match[3];
3648                   endtoken = star_match[4];
3649                 } else {
3650                   whitename = star_match[1];
3651                   blackname = star_match[3];
3652                   why = star_match[5];
3653                   endtoken = star_match[6];
3654                 }
3655
3656                 /* Game start messages */
3657                 if (strncmp(why, "Creating ", 9) == 0 ||
3658                     strncmp(why, "Continuing ", 11) == 0) {
3659                     gs_gamenum = gamenum;
3660                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3661                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3662 #if ZIPPY
3663                     if (appData.zippyPlay) {
3664                         ZippyGameStart(whitename, blackname);
3665                     }
3666 #endif /*ZIPPY*/
3667                     partnerBoardValid = FALSE; // [HGM] bughouse
3668                     continue;
3669                 }
3670
3671                 /* Game end messages */
3672                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3673                     ics_gamenum != gamenum) {
3674                     continue;
3675                 }
3676                 while (endtoken[0] == ' ') endtoken++;
3677                 switch (endtoken[0]) {
3678                   case '*':
3679                   default:
3680                     endtype = GameUnfinished;
3681                     break;
3682                   case '0':
3683                     endtype = BlackWins;
3684                     break;
3685                   case '1':
3686                     if (endtoken[1] == '/')
3687                       endtype = GameIsDrawn;
3688                     else
3689                       endtype = WhiteWins;
3690                     break;
3691                 }
3692                 GameEnds(endtype, why, GE_ICS);
3693 #if ZIPPY
3694                 if (appData.zippyPlay && first.initDone) {
3695                     ZippyGameEnd(endtype, why);
3696                     if (first.pr == NULL) {
3697                       /* Start the next process early so that we'll
3698                          be ready for the next challenge */
3699                       StartChessProgram(&first);
3700                     }
3701                     /* Send "new" early, in case this command takes
3702                        a long time to finish, so that we'll be ready
3703                        for the next challenge. */
3704                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3705                     Reset(TRUE, TRUE);
3706                 }
3707 #endif /*ZIPPY*/
3708                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3709                 continue;
3710             }
3711
3712             if (looking_at(buf, &i, "Removing game * from observation") ||
3713                 looking_at(buf, &i, "no longer observing game *") ||
3714                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3715                 if (gameMode == IcsObserving &&
3716                     atoi(star_match[0]) == ics_gamenum)
3717                   {
3718                       /* icsEngineAnalyze */
3719                       if (appData.icsEngineAnalyze) {
3720                             ExitAnalyzeMode();
3721                             ModeHighlight();
3722                       }
3723                       StopClocks();
3724                       gameMode = IcsIdle;
3725                       ics_gamenum = -1;
3726                       ics_user_moved = FALSE;
3727                   }
3728                 continue;
3729             }
3730
3731             if (looking_at(buf, &i, "no longer examining game *")) {
3732                 if (gameMode == IcsExamining &&
3733                     atoi(star_match[0]) == ics_gamenum)
3734                   {
3735                       gameMode = IcsIdle;
3736                       ics_gamenum = -1;
3737                       ics_user_moved = FALSE;
3738                   }
3739                 continue;
3740             }
3741
3742             /* Advance leftover_start past any newlines we find,
3743                so only partial lines can get reparsed */
3744             if (looking_at(buf, &i, "\n")) {
3745                 prevColor = curColor;
3746                 if (curColor != ColorNormal) {
3747                     if (oldi > next_out) {
3748                         SendToPlayer(&buf[next_out], oldi - next_out);
3749                         next_out = oldi;
3750                     }
3751                     Colorize(ColorNormal, FALSE);
3752                     curColor = ColorNormal;
3753                 }
3754                 if (started == STARTED_BOARD) {
3755                     started = STARTED_NONE;
3756                     parse[parse_pos] = NULLCHAR;
3757                     ParseBoard12(parse);
3758                     ics_user_moved = 0;
3759
3760                     /* Send premove here */
3761                     if (appData.premove) {
3762                       char str[MSG_SIZ];
3763                       if (currentMove == 0 &&
3764                           gameMode == IcsPlayingWhite &&
3765                           appData.premoveWhite) {
3766                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3767                         if (appData.debugMode)
3768                           fprintf(debugFP, "Sending premove:\n");
3769                         SendToICS(str);
3770                       } else if (currentMove == 1 &&
3771                                  gameMode == IcsPlayingBlack &&
3772                                  appData.premoveBlack) {
3773                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3774                         if (appData.debugMode)
3775                           fprintf(debugFP, "Sending premove:\n");
3776                         SendToICS(str);
3777                       } else if (gotPremove) {
3778                         gotPremove = 0;
3779                         ClearPremoveHighlights();
3780                         if (appData.debugMode)
3781                           fprintf(debugFP, "Sending premove:\n");
3782                           UserMoveEvent(premoveFromX, premoveFromY,
3783                                         premoveToX, premoveToY,
3784                                         premovePromoChar);
3785                       }
3786                     }
3787
3788                     /* Usually suppress following prompt */
3789                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3790                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3791                         if (looking_at(buf, &i, "*% ")) {
3792                             savingComment = FALSE;
3793                             suppressKibitz = 0;
3794                         }
3795                     }
3796                     next_out = i;
3797                 } else if (started == STARTED_HOLDINGS) {
3798                     int gamenum;
3799                     char new_piece[MSG_SIZ];
3800                     started = STARTED_NONE;
3801                     parse[parse_pos] = NULLCHAR;
3802                     if (appData.debugMode)
3803                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3804                                                         parse, currentMove);
3805                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3806                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3807                         if (gameInfo.variant == VariantNormal) {
3808                           /* [HGM] We seem to switch variant during a game!
3809                            * Presumably no holdings were displayed, so we have
3810                            * to move the position two files to the right to
3811                            * create room for them!
3812                            */
3813                           VariantClass newVariant;
3814                           switch(gameInfo.boardWidth) { // base guess on board width
3815                                 case 9:  newVariant = VariantShogi; break;
3816                                 case 10: newVariant = VariantGreat; break;
3817                                 default: newVariant = VariantCrazyhouse; break;
3818                           }
3819                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3820                           /* Get a move list just to see the header, which
3821                              will tell us whether this is really bug or zh */
3822                           if (ics_getting_history == H_FALSE) {
3823                             ics_getting_history = H_REQUESTED;
3824                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3825                             SendToICS(str);
3826                           }
3827                         }
3828                         new_piece[0] = NULLCHAR;
3829                         sscanf(parse, "game %d white [%s black [%s <- %s",
3830                                &gamenum, white_holding, black_holding,
3831                                new_piece);
3832                         white_holding[strlen(white_holding)-1] = NULLCHAR;