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