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