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