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