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