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