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