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