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