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