Add color zippy lines for winboard
[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;\r
1466 #ifdef WIN32\r
1467         /* For zippy color lines of winboard\r
1468          * cleanup for gcc compiler */\r
1469         int backup;\r
1470 #endif
1471     char *p;
1472
1473 #ifdef WIN32
1474     if (appData.debugMode) {
1475       if (!error) {
1476         fprintf(debugFP, "<ICS: ");
1477         show_bytes(debugFP, data, count);
1478         fprintf(debugFP, "\n");
1479       }
1480     }
1481 #endif
1482
1483     if (count > 0) {
1484         /* If last read ended with a partial line that we couldn't parse,
1485            prepend it to the new read and try again. */
1486         if (leftover_len > 0) {
1487             for (i=0; i<leftover_len; i++)
1488               buf[i] = buf[leftover_start + i];
1489         }
1490
1491         /* Copy in new characters, removing nulls and \r's */
1492         buf_len = leftover_len;
1493         for (i = 0; i < count; i++) {
1494             if (data[i] != NULLCHAR && data[i] != '\r')
1495               buf[buf_len++] = data[i];
1496         }
1497
1498         buf[buf_len] = NULLCHAR;
1499         next_out = leftover_len;
1500         leftover_start = 0;
1501         
1502         i = 0;
1503         while (i < buf_len) {
1504             /* Deal with part of the TELNET option negotiation
1505                protocol.  We refuse to do anything beyond the
1506                defaults, except that we allow the WILL ECHO option,
1507                which ICS uses to turn off password echoing when we are
1508                directly connected to it.  We reject this option
1509                if localLineEditing mode is on (always on in xboard)
1510                and we are talking to port 23, which might be a real
1511                telnet server that will try to keep WILL ECHO on permanently.
1512              */
1513             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1514                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1515                 unsigned char option;
1516                 oldi = i;
1517                 switch ((unsigned char) buf[++i]) {
1518                   case TN_WILL:
1519                     if (appData.debugMode)
1520                       fprintf(debugFP, "\n<WILL ");
1521                     switch (option = (unsigned char) buf[++i]) {
1522                       case TN_ECHO:
1523                         if (appData.debugMode)
1524                           fprintf(debugFP, "ECHO ");
1525                         /* Reply only if this is a change, according
1526                            to the protocol rules. */
1527                         if (remoteEchoOption) break;
1528                         if (appData.localLineEditing &&
1529                             atoi(appData.icsPort) == TN_PORT) {
1530                             TelnetRequest(TN_DONT, TN_ECHO);
1531                         } else {
1532                             EchoOff();
1533                             TelnetRequest(TN_DO, TN_ECHO);
1534                             remoteEchoOption = TRUE;
1535                         }
1536                         break;
1537                       default:
1538                         if (appData.debugMode)
1539                           fprintf(debugFP, "%d ", option);
1540                         /* Whatever this is, we don't want it. */
1541                         TelnetRequest(TN_DONT, option);
1542                         break;
1543                     }
1544                     break;
1545                   case TN_WONT:
1546                     if (appData.debugMode)
1547                       fprintf(debugFP, "\n<WONT ");
1548                     switch (option = (unsigned char) buf[++i]) {
1549                       case TN_ECHO:
1550                         if (appData.debugMode)
1551                           fprintf(debugFP, "ECHO ");
1552                         /* Reply only if this is a change, according
1553                            to the protocol rules. */
1554                         if (!remoteEchoOption) break;
1555                         EchoOn();
1556                         TelnetRequest(TN_DONT, TN_ECHO);
1557                         remoteEchoOption = FALSE;
1558                         break;
1559                       default:
1560                         if (appData.debugMode)
1561                           fprintf(debugFP, "%d ", (unsigned char) option);
1562                         /* Whatever this is, it must already be turned
1563                            off, because we never agree to turn on
1564                            anything non-default, so according to the
1565                            protocol rules, we don't reply. */
1566                         break;
1567                     }
1568                     break;
1569                   case TN_DO:
1570                     if (appData.debugMode)
1571                       fprintf(debugFP, "\n<DO ");
1572                     switch (option = (unsigned char) buf[++i]) {
1573                       default:
1574                         /* Whatever this is, we refuse to do it. */
1575                         if (appData.debugMode)
1576                           fprintf(debugFP, "%d ", option);
1577                         TelnetRequest(TN_WONT, option);
1578                         break;
1579                     }
1580                     break;
1581                   case TN_DONT:
1582                     if (appData.debugMode)
1583                       fprintf(debugFP, "\n<DONT ");
1584                     switch (option = (unsigned char) buf[++i]) {
1585                       default:
1586                         if (appData.debugMode)
1587                           fprintf(debugFP, "%d ", option);
1588                         /* Whatever this is, we are already not doing
1589                            it, because we never agree to do anything
1590                            non-default, so according to the protocol
1591                            rules, we don't reply. */
1592                         break;
1593                     }
1594                     break;
1595                   case TN_IAC:
1596                     if (appData.debugMode)
1597                       fprintf(debugFP, "\n<IAC ");
1598                     /* Doubled IAC; pass it through */
1599                     i--;
1600                     break;
1601                   default:
1602                     if (appData.debugMode)
1603                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1604                     /* Drop all other telnet commands on the floor */
1605                     break;
1606                 }
1607                 if (oldi > next_out)
1608                   SendToPlayer(&buf[next_out], oldi - next_out);
1609                 if (++i > next_out)
1610                   next_out = i;
1611                 continue;
1612             }
1613                 
1614             /* OK, this at least will *usually* work */
1615             if (!loggedOn && looking_at(buf, &i, "ics%")) {
1616                 loggedOn = TRUE;
1617             }
1618             
1619             if (loggedOn && !intfSet) {
1620                 if (ics_type == ICS_ICC) {
1621                   sprintf(str,
1622                           "/set-quietly interface %s\n/set-quietly style 12\n",
1623                           programVersion);
1624
1625                 } else if (ics_type == ICS_CHESSNET) {
1626                   sprintf(str, "/style 12\n");
1627                 } else {
1628                   strcpy(str, "alias $ @\n$set interface ");
1629                   strcat(str, programVersion);
1630                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1631 #ifdef WIN32
1632                   strcat(str, "$iset nohighlight 1\n");
1633 #endif
1634                   strcat(str, "$iset lock 1\n$style 12\n");
1635                 }
1636                 SendToICS(str);
1637                 intfSet = TRUE;
1638             }
1639
1640             if (started == STARTED_COMMENT) {
1641                 /* Accumulate characters in comment */
1642                 parse[parse_pos++] = buf[i];
1643                 if (buf[i] == '\n') {
1644                     parse[parse_pos] = NULLCHAR;
1645                     AppendComment(forwardMostMove, StripHighlight(parse));
1646                     started = STARTED_NONE;
1647                 } else {
1648                     /* Don't match patterns against characters in chatter */
1649                     i++;
1650                     continue;
1651                 }
1652             }
1653             if (started == STARTED_CHATTER) {
1654                 if (buf[i] != '\n') {
1655                     /* Don't match patterns against characters in chatter */
1656                     i++;
1657                     continue;
1658                 }
1659                 started = STARTED_NONE;
1660             }
1661
1662             /* Kludge to deal with rcmd protocol */
1663             if (firstTime && looking_at(buf, &i, "\001*")) {
1664                 DisplayFatalError(&buf[1], 0, 1);
1665                 continue;
1666             } else {
1667                 firstTime = FALSE;
1668             }
1669
1670             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1671                 ics_type = ICS_ICC;
1672                 ics_prefix = "/";
1673                 if (appData.debugMode)
1674                   fprintf(debugFP, "ics_type %d\n", ics_type);
1675                 continue;
1676             }
1677             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1678                 ics_type = ICS_FICS;
1679                 ics_prefix = "$";
1680                 if (appData.debugMode)
1681                   fprintf(debugFP, "ics_type %d\n", ics_type);
1682                 continue;
1683             }
1684             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1685                 ics_type = ICS_CHESSNET;
1686                 ics_prefix = "/";
1687                 if (appData.debugMode)
1688                   fprintf(debugFP, "ics_type %d\n", ics_type);
1689                 continue;
1690             }
1691
1692             if (!loggedOn &&
1693                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1694                  looking_at(buf, &i, "Logging you in as \"*\"") ||
1695                  looking_at(buf, &i, "will be \"*\""))) {
1696               strcpy(ics_handle, star_match[0]);
1697               continue;
1698             }
1699
1700             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1701               char buf[MSG_SIZ];
1702               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1703               DisplayIcsInteractionTitle(buf);
1704               have_set_title = TRUE;
1705             }
1706
1707             /* skip finger notes */
1708             if (started == STARTED_NONE &&
1709                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1710                  (buf[i] == '1' && buf[i+1] == '0')) &&
1711                 buf[i+2] == ':' && buf[i+3] == ' ') {
1712               started = STARTED_CHATTER;
1713               i += 3;
1714               continue;
1715             }
1716
1717             /* skip formula vars */
1718             if (started == STARTED_NONE &&
1719                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1720               started = STARTED_CHATTER;
1721               i += 3;
1722               continue;
1723             }
1724
1725             oldi = i;
1726             if (appData.zippyTalk || appData.zippyPlay) {
1727 #if ZIPPY\r
1728         #ifdef WIN32\r
1729                 /* Backup adress for color zippy lines */\r
1730                 backup = i;\r
1731                 if (first.initDone && loggedOn == TRUE)\r
1732                         if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
1733                                 (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
1734                 #else
1735                 if (ZippyControl(buf, &i) ||
1736                     ZippyConverse(buf, &i) ||
1737                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
1738                     loggedOn = TRUE;
1739                     continue;
1740                 }\r
1741         #endif
1742 #endif
1743             }
1744                 if (/* Don't color "message" or "messages" output */
1745                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1746                     looking_at(buf, &i, "*. * at *:*: ") ||
1747                     looking_at(buf, &i, "--* (*:*): ") ||
1748                     /* Regular tells and says */
1749                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1750                     looking_at(buf, &i, "* (your partner) tells you: ") ||
1751                     looking_at(buf, &i, "* says: ") ||
1752                     /* Message notifications (same color as tells) */
1753                     looking_at(buf, &i, "* has left a message ") ||
1754                     looking_at(buf, &i, "* just sent you a message:\n") ||
1755                     /* Whispers and kibitzes */
1756                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1757                     looking_at(buf, &i, "* kibitzes: ") ||
1758                     /* Channel tells */
1759                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1760
1761                   if (tkind == 1 && strchr(star_match[0], ':')) {
1762                       /* Avoid "tells you:" spoofs in channels */
1763                      tkind = 3;
1764                   }
1765                   if (star_match[0][0] == NULLCHAR ||
1766                       strchr(star_match[0], ' ') ||
1767                       (tkind == 3 && strchr(star_match[1], ' '))) {
1768                     /* Reject bogus matches */
1769                     i = oldi;
1770                   } else {
1771                     if (appData.colorize) {
1772                       if (oldi > next_out) {
1773                         SendToPlayer(&buf[next_out], oldi - next_out);
1774                         next_out = oldi;
1775                       }
1776                       switch (tkind) {
1777                       case 1:
1778                         Colorize(ColorTell, FALSE);
1779                         curColor = ColorTell;
1780                         break;
1781                       case 2:
1782                         Colorize(ColorKibitz, FALSE);
1783                         curColor = ColorKibitz;
1784                         break;
1785                       case 3:
1786                         p = strrchr(star_match[1], '(');
1787                         if (p == NULL) {
1788                           p = star_match[1];
1789                         } else {
1790                           p++;
1791                         }
1792                         if (atoi(p) == 1) {
1793                           Colorize(ColorChannel1, FALSE);
1794                           curColor = ColorChannel1;
1795                         } else {
1796                           Colorize(ColorChannel, FALSE);
1797                           curColor = ColorChannel;
1798                         }
1799                         break;
1800                       case 5:
1801                         curColor = ColorNormal;
1802                         break;
1803                       }
1804                     }
1805                     if (started == STARTED_NONE && appData.autoComment &&
1806                         (gameMode == IcsObserving ||
1807                          gameMode == IcsPlayingWhite ||
1808                          gameMode == IcsPlayingBlack)) {
1809                       parse_pos = i - oldi;
1810                       memcpy(parse, &buf[oldi], parse_pos);
1811                       parse[parse_pos] = NULLCHAR;
1812                       started = STARTED_COMMENT;
1813                       savingComment = TRUE;
1814                     } else {
1815                       started = STARTED_CHATTER;
1816                       savingComment = FALSE;
1817                     }
1818                     loggedOn = TRUE;
1819                     continue;
1820                   }
1821                 }
1822
1823                 if (looking_at(buf, &i, "* s-shouts: ") ||
1824                     looking_at(buf, &i, "* c-shouts: ")) {
1825                     if (appData.colorize) {
1826                         if (oldi > next_out) {
1827                             SendToPlayer(&buf[next_out], oldi - next_out);
1828                             next_out = oldi;
1829                         }
1830                         Colorize(ColorSShout, FALSE);
1831                         curColor = ColorSShout;
1832                     }
1833                     loggedOn = TRUE;
1834                     started = STARTED_CHATTER;
1835                     continue;
1836                 }
1837
1838                 if (looking_at(buf, &i, "--->")) {
1839                     loggedOn = TRUE;
1840                     continue;
1841                 }
1842
1843                 if (looking_at(buf, &i, "* shouts: ") ||
1844                     looking_at(buf, &i, "--> ")) {
1845                     if (appData.colorize) {
1846                         if (oldi > next_out) {
1847                             SendToPlayer(&buf[next_out], oldi - next_out);
1848                             next_out = oldi;
1849                         }
1850                         Colorize(ColorShout, FALSE);
1851                         curColor = ColorShout;
1852                     }
1853                     loggedOn = TRUE;
1854                     started = STARTED_CHATTER;
1855                     continue;
1856                 }
1857
1858                 if (looking_at( buf, &i, "Challenge:")) {
1859                     if (appData.colorize) {
1860                         if (oldi > next_out) {
1861                             SendToPlayer(&buf[next_out], oldi - next_out);
1862                             next_out = oldi;
1863                         }
1864                         Colorize(ColorChallenge, FALSE);
1865                         curColor = ColorChallenge;
1866                     }
1867                     loggedOn = TRUE;
1868                     continue;
1869                 }
1870
1871                 if (looking_at(buf, &i, "* offers you") ||
1872                     looking_at(buf, &i, "* offers to be") ||
1873                     looking_at(buf, &i, "* would like to") ||
1874                     looking_at(buf, &i, "* requests to") ||
1875                     looking_at(buf, &i, "Your opponent offers") ||
1876                     looking_at(buf, &i, "Your opponent requests")) {
1877
1878                     if (appData.colorize) {
1879                         if (oldi > next_out) {
1880                             SendToPlayer(&buf[next_out], oldi - next_out);
1881                             next_out = oldi;
1882                         }
1883                         Colorize(ColorRequest, FALSE);
1884                         curColor = ColorRequest;
1885                     }
1886                     continue;
1887                 }
1888
1889                 if (looking_at(buf, &i, "* (*) seeking")) {
1890                     if (appData.colorize) {
1891                         if (oldi > next_out) {
1892                             SendToPlayer(&buf[next_out], oldi - next_out);
1893                             next_out = oldi;
1894                         }
1895                         Colorize(ColorSeek, FALSE);
1896                         curColor = ColorSeek;
1897                     }
1898                     continue;
1899             }
1900
1901             if (looking_at(buf, &i, "\\   ")) {
1902                 if (prevColor != ColorNormal) {
1903                     if (oldi > next_out) {
1904                         SendToPlayer(&buf[next_out], oldi - next_out);
1905                         next_out = oldi;
1906                     }
1907                     Colorize(prevColor, TRUE);
1908                     curColor = prevColor;
1909                 }
1910                 if (savingComment) {
1911                     parse_pos = i - oldi;
1912                     memcpy(parse, &buf[oldi], parse_pos);
1913                     parse[parse_pos] = NULLCHAR;
1914                     started = STARTED_COMMENT;
1915                 } else {
1916                     started = STARTED_CHATTER;
1917                 }
1918                 continue;
1919             }
1920
1921             if (looking_at(buf, &i, "Black Strength :") ||
1922                 looking_at(buf, &i, "<<< style 10 board >>>") ||
1923                 looking_at(buf, &i, "<10>") ||
1924                 looking_at(buf, &i, "#@#")) {
1925                 /* Wrong board style */
1926                 loggedOn = TRUE;
1927                 SendToICS(ics_prefix);
1928                 SendToICS("set style 12\n");
1929                 SendToICS(ics_prefix);
1930                 SendToICS("refresh\n");
1931                 continue;
1932             }
1933             
1934             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1935                 ICSInitScript();
1936                 have_sent_ICS_logon = 1;
1937                 continue;
1938             }
1939               
1940             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
1941                 (looking_at(buf, &i, "\n<12> ") ||
1942                  looking_at(buf, &i, "<12> "))) {
1943                 loggedOn = TRUE;
1944                 if (oldi > next_out) {
1945                     SendToPlayer(&buf[next_out], oldi - next_out);
1946                 }
1947                 next_out = i;
1948                 started = STARTED_BOARD;
1949                 parse_pos = 0;
1950                 continue;
1951             }
1952
1953             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1954                 looking_at(buf, &i, "<b1> ")) {
1955                 if (oldi > next_out) {
1956                     SendToPlayer(&buf[next_out], oldi - next_out);
1957                 }
1958                 next_out = i;
1959                 started = STARTED_HOLDINGS;
1960                 parse_pos = 0;
1961                 continue;
1962             }
1963
1964             if (looking_at(buf, &i, "* *vs. * *--- *")) {
1965                 loggedOn = TRUE;
1966                 /* Header for a move list -- first line */
1967
1968                 switch (ics_getting_history) {
1969                   case H_FALSE:
1970                     switch (gameMode) {
1971                       case IcsIdle:
1972                       case BeginningOfGame:
1973                         /* User typed "moves" or "oldmoves" while we
1974                            were idle.  Pretend we asked for these
1975                            moves and soak them up so user can step
1976                            through them and/or save them.
1977                            */
1978                         Reset(FALSE, TRUE);
1979                         gameMode = IcsObserving;
1980                         ModeHighlight();
1981                         ics_gamenum = -1;
1982                         ics_getting_history = H_GOT_UNREQ_HEADER;
1983                         break;
1984                       case EditGame: /*?*/
1985                       case EditPosition: /*?*/
1986                         /* Should above feature work in these modes too? */
1987                         /* For now it doesn't */
1988                         ics_getting_history = H_GOT_UNWANTED_HEADER;
1989                         break;
1990                       default:
1991                         ics_getting_history = H_GOT_UNWANTED_HEADER;
1992                         break;
1993                     }
1994                     break;
1995                   case H_REQUESTED:
1996                     /* Is this the right one? */
1997                     if (gameInfo.white && gameInfo.black &&
1998                         strcmp(gameInfo.white, star_match[0]) == 0 &&
1999                         strcmp(gameInfo.black, star_match[2]) == 0) {
2000                         /* All is well */
2001                         ics_getting_history = H_GOT_REQ_HEADER;
2002                     }
2003                     break;
2004                   case H_GOT_REQ_HEADER:
2005                   case H_GOT_UNREQ_HEADER:
2006                   case H_GOT_UNWANTED_HEADER:
2007                   case H_GETTING_MOVES:
2008                     /* Should not happen */
2009                     DisplayError(_("Error gathering move list: two headers"), 0);
2010                     ics_getting_history = H_FALSE;
2011                     break;
2012                 }
2013
2014                 /* Save player ratings into gameInfo if needed */
2015                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2016                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2017                     (gameInfo.whiteRating == -1 ||
2018                      gameInfo.blackRating == -1)) {
2019
2020                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2021                     gameInfo.blackRating = string_to_rating(star_match[3]);
2022                     if (appData.debugMode)
2023                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2024                               gameInfo.whiteRating, gameInfo.blackRating);
2025                 }
2026                 continue;
2027             }
2028
2029             if (looking_at(buf, &i,
2030               "* * match, initial time: * minute*, increment: * second")) {
2031                 /* Header for a move list -- second line */
2032                 /* Initial board will follow if this is a wild game */
2033
2034                 if (gameInfo.event != NULL) free(gameInfo.event);
2035                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2036                 gameInfo.event = StrSave(str);
2037                 gameInfo.variant = StringToVariant(gameInfo.event);
2038                 continue;
2039             }
2040
2041             if (looking_at(buf, &i, "Move  ")) {
2042                 /* Beginning of a move list */
2043                 switch (ics_getting_history) {
2044                   case H_FALSE:
2045                     /* Normally should not happen */
2046                     /* Maybe user hit reset while we were parsing */
2047                     break;
2048                   case H_REQUESTED:
2049                     /* Happens if we are ignoring a move list that is not
2050                      * the one we just requested.  Common if the user
2051                      * tries to observe two games without turning off
2052                      * getMoveList */
2053                     break;
2054                   case H_GETTING_MOVES:
2055                     /* Should not happen */
2056                     DisplayError(_("Error gathering move list: nested"), 0);
2057                     ics_getting_history = H_FALSE;
2058                     break;
2059                   case H_GOT_REQ_HEADER:
2060                     ics_getting_history = H_GETTING_MOVES;
2061                     started = STARTED_MOVES;
2062                     parse_pos = 0;
2063                     if (oldi > next_out) {
2064                         SendToPlayer(&buf[next_out], oldi - next_out);
2065                     }
2066                     break;
2067                   case H_GOT_UNREQ_HEADER:
2068                     ics_getting_history = H_GETTING_MOVES;
2069                     started = STARTED_MOVES_NOHIDE;
2070                     parse_pos = 0;
2071                     break;
2072                   case H_GOT_UNWANTED_HEADER:
2073                     ics_getting_history = H_FALSE;
2074                     break;
2075                 }
2076                 continue;
2077             }                           
2078             
2079             if (looking_at(buf, &i, "% ") ||
2080                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2081                  && looking_at(buf, &i, "}*"))) {
2082                 savingComment = FALSE;
2083                 switch (started) {
2084                   case STARTED_MOVES:
2085                   case STARTED_MOVES_NOHIDE:
2086                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2087                     parse[parse_pos + i - oldi] = NULLCHAR;
2088                     ParseGameHistory(parse);
2089 #if ZIPPY
2090                     if (appData.zippyPlay && first.initDone) {
2091                         FeedMovesToProgram(&first, forwardMostMove);
2092                         if (gameMode == IcsPlayingWhite) {
2093                             if (WhiteOnMove(forwardMostMove)) {
2094                                 if (first.sendTime) {
2095                                   if (first.useColors) {
2096                                     SendToProgram("black\n", &first); 
2097                                   }
2098                                   SendTimeRemaining(&first, TRUE);
2099                                 }
2100                                 if (first.useColors) {
2101                                   SendToProgram("white\ngo\n", &first);
2102                                 } else {
2103                                   SendToProgram("go\n", &first);
2104                                 }
2105                                 first.maybeThinking = TRUE;
2106                             } else {
2107                                 if (first.usePlayother) {
2108                                   if (first.sendTime) {
2109                                     SendTimeRemaining(&first, TRUE);
2110                                   }
2111                                   SendToProgram("playother\n", &first);
2112                                   firstMove = FALSE;
2113                                 } else {
2114                                   firstMove = TRUE;
2115                                 }
2116                             }
2117                         } else if (gameMode == IcsPlayingBlack) {
2118                             if (!WhiteOnMove(forwardMostMove)) {
2119                                 if (first.sendTime) {
2120                                   if (first.useColors) {
2121                                     SendToProgram("white\n", &first);
2122                                   }
2123                                   SendTimeRemaining(&first, FALSE);
2124                                 }
2125                                 if (first.useColors) {
2126                                   SendToProgram("black\ngo\n", &first);
2127                                 } else {
2128                                   SendToProgram("go\n", &first);
2129                                 }
2130                                 first.maybeThinking = TRUE;
2131                             } else {
2132                                 if (first.usePlayother) {
2133                                   if (first.sendTime) {
2134                                     SendTimeRemaining(&first, FALSE);
2135                                   }
2136                                   SendToProgram("playother\n", &first);
2137                                   firstMove = FALSE;
2138                                 } else {
2139                                   firstMove = TRUE;
2140                                 }
2141                             }
2142                         }                       
2143                     }
2144 #endif
2145                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2146                         /* Moves came from oldmoves or moves command
2147                            while we weren't doing anything else.
2148                            */
2149                         currentMove = forwardMostMove;
2150                         ClearHighlights();/*!!could figure this out*/
2151                         flipView = appData.flipView;
2152                         DrawPosition(FALSE, boards[currentMove]);
2153                         DisplayBothClocks();
2154                         sprintf(str, "%s vs. %s",
2155                                 gameInfo.white, gameInfo.black);
2156                         DisplayTitle(str);
2157                         gameMode = IcsIdle;
2158                     } else {
2159                         /* Moves were history of an active game */
2160                         if (gameInfo.resultDetails != NULL) {
2161                             free(gameInfo.resultDetails);
2162                             gameInfo.resultDetails = NULL;
2163                         }
2164                     }
2165                     HistorySet(parseList, backwardMostMove,
2166                                forwardMostMove, currentMove-1);
2167                     DisplayMove(currentMove - 1);
2168                     if (started == STARTED_MOVES) next_out = i;
2169                     started = STARTED_NONE;
2170                     ics_getting_history = H_FALSE;
2171                     break;
2172
2173                   case STARTED_OBSERVE:
2174                     started = STARTED_NONE;
2175                     SendToICS(ics_prefix);
2176                     SendToICS("refresh\n");
2177                     break;
2178
2179                   default:
2180                     break;
2181                 }
2182                 continue;
2183             }
2184             
2185             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2186                  started == STARTED_HOLDINGS ||
2187                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2188                 /* Accumulate characters in move list or board */
2189                 parse[parse_pos++] = buf[i];
2190             }
2191             
2192             /* Start of game messages.  Mostly we detect start of game
2193                when the first board image arrives.  On some versions
2194                of the ICS, though, we need to do a "refresh" after starting
2195                to observe in order to get the current board right away. */
2196             if (looking_at(buf, &i, "Adding game * to observation list")) {
2197                 started = STARTED_OBSERVE;
2198                 continue;
2199             }
2200
2201             /* Handle auto-observe */
2202             if (appData.autoObserve &&
2203                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2204                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2205                 char *player;
2206                 /* Choose the player that was highlighted, if any. */
2207                 if (star_match[0][0] == '\033' ||
2208                     star_match[1][0] != '\033') {
2209                     player = star_match[0];
2210                 } else {
2211                     player = star_match[2];
2212                 }
2213                 sprintf(str, "%sobserve %s\n",
2214                         ics_prefix, StripHighlightAndTitle(player));
2215                 SendToICS(str);
2216
2217                 /* Save ratings from notify string */
2218                 strcpy(player1Name, star_match[0]);
2219                 player1Rating = string_to_rating(star_match[1]);
2220                 strcpy(player2Name, star_match[2]);
2221                 player2Rating = string_to_rating(star_match[3]);
2222
2223                 if (appData.debugMode)
2224                   fprintf(debugFP, 
2225                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2226                           player1Name, player1Rating,
2227                           player2Name, player2Rating);
2228
2229                 continue;
2230             }
2231
2232             /* Deal with automatic examine mode after a game,
2233                and with IcsObserving -> IcsExamining transition */
2234             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2235                 looking_at(buf, &i, "has made you an examiner of game *")) {
2236
2237                 int gamenum = atoi(star_match[0]);
2238                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2239                     gamenum == ics_gamenum) {
2240                     /* We were already playing or observing this game;
2241                        no need to refetch history */
2242                     gameMode = IcsExamining;
2243                     if (pausing) {
2244                         pauseExamForwardMostMove = forwardMostMove;
2245                     } else if (currentMove < forwardMostMove) {
2246                         ForwardInner(forwardMostMove);
2247                     }
2248                 } else {
2249                     /* I don't think this case really can happen */
2250                     SendToICS(ics_prefix);
2251                     SendToICS("refresh\n");
2252                 }
2253                 continue;
2254             }    
2255             
2256             /* Error messages */
2257             if (ics_user_moved) {
2258                 if (looking_at(buf, &i, "Illegal move") ||
2259                     looking_at(buf, &i, "Not a legal move") ||
2260                     looking_at(buf, &i, "Your king is in check") ||
2261                     looking_at(buf, &i, "It isn't your turn") ||
2262                     looking_at(buf, &i, "It is not your move")) {
2263                     /* Illegal move */
2264                     ics_user_moved = 0;
2265                     if (forwardMostMove > backwardMostMove) {
2266                         currentMove = --forwardMostMove;
2267                         DisplayMove(currentMove - 1); /* before DMError */
2268                         DisplayMoveError("Illegal move (rejected by ICS)");
2269                         DrawPosition(FALSE, boards[currentMove]);
2270                         SwitchClocks();
2271                         DisplayBothClocks();
2272                     }
2273                     continue;
2274                 }
2275             }
2276
2277             if (looking_at(buf, &i, "still have time") ||
2278                 looking_at(buf, &i, "not out of time") ||
2279                 looking_at(buf, &i, "either player is out of time") ||
2280                 looking_at(buf, &i, "has timeseal; checking")) {
2281                 /* We must have called his flag a little too soon */
2282                 whiteFlag = blackFlag = FALSE;
2283                 continue;
2284             }
2285
2286             if (looking_at(buf, &i, "added * seconds to") ||
2287                 looking_at(buf, &i, "seconds were added to")) {
2288                 /* Update the clocks */
2289                 SendToICS(ics_prefix);
2290                 SendToICS("refresh\n");
2291                 continue;
2292             }
2293
2294             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2295                 ics_clock_paused = TRUE;
2296                 StopClocks();
2297                 continue;
2298             }
2299
2300             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2301                 ics_clock_paused = FALSE;
2302                 StartClocks();
2303                 continue;
2304             }
2305
2306             /* Grab player ratings from the Creating: message.
2307                Note we have to check for the special case when
2308                the ICS inserts things like [white] or [black]. */
2309             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2310                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2311                 /* star_matches:
2312                    0    player 1 name (not necessarily white)
2313                    1    player 1 rating
2314                    2    empty, white, or black (IGNORED)
2315                    3    player 2 name (not necessarily black)
2316                    4    player 2 rating
2317                    
2318                    The names/ratings are sorted out when the game
2319                    actually starts (below).
2320                 */
2321                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2322                 player1Rating = string_to_rating(star_match[1]);
2323                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2324                 player2Rating = string_to_rating(star_match[4]);
2325
2326                 if (appData.debugMode)
2327                   fprintf(debugFP, 
2328                           "Ratings from 'Creating:' %s %d, %s %d\n",
2329                           player1Name, player1Rating,
2330                           player2Name, player2Rating);
2331
2332                 continue;
2333             }
2334             
2335             /* Improved generic start/end-of-game messages */
2336             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2337                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2338                 /* If tkind == 0: */
2339                 /* star_match[0] is the game number */
2340                 /*           [1] is the white player's name */
2341                 /*           [2] is the black player's name */
2342                 /* For end-of-game: */
2343                 /*           [3] is the reason for the game end */
2344                 /*           [4] is a PGN end game-token, preceded by " " */
2345                 /* For start-of-game: */
2346                 /*           [3] begins with "Creating" or "Continuing" */
2347                 /*           [4] is " *" or empty (don't care). */
2348                 int gamenum = atoi(star_match[0]);
2349                 char *whitename, *blackname, *why, *endtoken;
2350                 ChessMove endtype = (ChessMove) 0;
2351
2352                 if (tkind == 0) {
2353                   whitename = star_match[1];
2354                   blackname = star_match[2];
2355                   why = star_match[3];
2356                   endtoken = star_match[4];
2357                 } else {
2358                   whitename = star_match[1];
2359                   blackname = star_match[3];
2360                   why = star_match[5];
2361                   endtoken = star_match[6];
2362                 }
2363
2364                 /* Game start messages */
2365                 if (strncmp(why, "Creating ", 9) == 0 ||
2366                     strncmp(why, "Continuing ", 11) == 0) {
2367                     gs_gamenum = gamenum;
2368                     strcpy(gs_kind, strchr(why, ' ') + 1);
2369 #if ZIPPY
2370                     if (appData.zippyPlay) {
2371                         ZippyGameStart(whitename, blackname);
2372                     }
2373 #endif /*ZIPPY*/
2374                     continue;
2375                 }
2376
2377                 /* Game end messages */
2378                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2379                     ics_gamenum != gamenum) {
2380                     continue;
2381                 }
2382                 while (endtoken[0] == ' ') endtoken++;
2383                 switch (endtoken[0]) {
2384                   case '*':
2385                   default:
2386                     endtype = GameUnfinished;
2387                     break;
2388                   case '0':
2389                     endtype = BlackWins;
2390                     break;
2391                   case '1':
2392                     if (endtoken[1] == '/')
2393                       endtype = GameIsDrawn;
2394                     else
2395                       endtype = WhiteWins;
2396                     break;
2397                 }
2398                 GameEnds(endtype, why, GE_ICS);
2399 #if ZIPPY
2400                 if (appData.zippyPlay && first.initDone) {
2401                     ZippyGameEnd(endtype, why);
2402                     if (first.pr == NULL) {
2403                       /* Start the next process early so that we'll
2404                          be ready for the next challenge */
2405                       StartChessProgram(&first);
2406                     }
2407                     /* Send "new" early, in case this command takes
2408                        a long time to finish, so that we'll be ready
2409                        for the next challenge. */
2410                     Reset(TRUE, TRUE);
2411                 }
2412 #endif /*ZIPPY*/
2413                 continue;
2414             }
2415
2416             if (looking_at(buf, &i, "Removing game * from observation") ||
2417                 looking_at(buf, &i, "no longer observing game *") ||
2418                 looking_at(buf, &i, "Game * (*) has no examiners")) {
2419                 if (gameMode == IcsObserving &&
2420                     atoi(star_match[0]) == ics_gamenum)
2421                   {
2422                       StopClocks();
2423                       gameMode = IcsIdle;
2424                       ics_gamenum = -1;
2425                       ics_user_moved = FALSE;
2426                   }
2427                 continue;
2428             }
2429
2430             if (looking_at(buf, &i, "no longer examining game *")) {
2431                 if (gameMode == IcsExamining &&
2432                     atoi(star_match[0]) == ics_gamenum)
2433                   {
2434                       gameMode = IcsIdle;
2435                       ics_gamenum = -1;
2436                       ics_user_moved = FALSE;
2437                   }
2438                 continue;
2439             }
2440
2441             /* Advance leftover_start past any newlines we find,
2442                so only partial lines can get reparsed */
2443             if (looking_at(buf, &i, "\n")) {
2444                 prevColor = curColor;
2445                 if (curColor != ColorNormal) {
2446                     if (oldi > next_out) {
2447                         SendToPlayer(&buf[next_out], oldi - next_out);
2448                         next_out = oldi;
2449                     }
2450                     Colorize(ColorNormal, FALSE);
2451                     curColor = ColorNormal;
2452                 }
2453                 if (started == STARTED_BOARD) {
2454                     started = STARTED_NONE;
2455                     parse[parse_pos] = NULLCHAR;
2456                     ParseBoard12(parse);
2457                     ics_user_moved = 0;
2458
2459                     /* Send premove here */
2460                     if (appData.premove) {
2461                       char str[MSG_SIZ];
2462                       if (currentMove == 0 &&
2463                           gameMode == IcsPlayingWhite &&
2464                           appData.premoveWhite) {
2465                         sprintf(str, "%s%s\n", ics_prefix,
2466                                 appData.premoveWhiteText);
2467                         if (appData.debugMode)
2468                           fprintf(debugFP, "Sending premove:\n");
2469                         SendToICS(str);
2470                       } else if (currentMove == 1 &&
2471                                  gameMode == IcsPlayingBlack &&
2472                                  appData.premoveBlack) {
2473                         sprintf(str, "%s%s\n", ics_prefix,
2474                                 appData.premoveBlackText);
2475                         if (appData.debugMode)
2476                           fprintf(debugFP, "Sending premove:\n");
2477                         SendToICS(str);
2478                       } else if (gotPremove) {
2479                         gotPremove = 0;
2480                         ClearPremoveHighlights();
2481                         if (appData.debugMode)
2482                           fprintf(debugFP, "Sending premove:\n");
2483                           UserMoveEvent(premoveFromX, premoveFromY, 
2484                                         premoveToX, premoveToY, 
2485                                         premovePromoChar);
2486                       }
2487                     }
2488
2489                     /* Usually suppress following prompt */
2490                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2491                         if (looking_at(buf, &i, "*% ")) {
2492                             savingComment = FALSE;
2493                         }
2494                     }
2495                     next_out = i;
2496                 } else if (started == STARTED_HOLDINGS) {
2497                     int gamenum;
2498                     char new_piece[MSG_SIZ];
2499                     started = STARTED_NONE;
2500                     parse[parse_pos] = NULLCHAR;
2501                     if (appData.debugMode)
2502                       fprintf(debugFP, "Parsing holdings: %s\n", parse);
2503                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
2504                         gamenum == ics_gamenum) {
2505                         if (gameInfo.variant == VariantNormal) {
2506                           gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2507                           /* Get a move list just to see the header, which
2508                              will tell us whether this is really bug or zh */
2509                           if (ics_getting_history == H_FALSE) {
2510                             ics_getting_history = H_REQUESTED;
2511                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2512                             SendToICS(str);
2513                           }
2514                         }
2515                         new_piece[0] = NULLCHAR;
2516                         sscanf(parse, "game %d white [%s black [%s <- %s",
2517                                &gamenum, white_holding, black_holding,
2518                                new_piece);
2519                         white_holding[strlen(white_holding)-1] = NULLCHAR;
2520                         black_holding[strlen(black_holding)-1] = NULLCHAR;
2521 #if ZIPPY
2522                         if (appData.zippyPlay && first.initDone) {
2523                             ZippyHoldings(white_holding, black_holding,
2524                                           new_piece);
2525                         }
2526 #endif /*ZIPPY*/
2527                         if (tinyLayout || smallLayout) {
2528                             char wh[16], bh[16];
2529                             PackHolding(wh, white_holding);
2530                             PackHolding(bh, black_holding);
2531                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
2532                                     gameInfo.white, gameInfo.black);
2533                         } else {
2534                             sprintf(str, "%s [%s] vs. %s [%s]",
2535                                     gameInfo.white, white_holding,
2536                                     gameInfo.black, black_holding);
2537                         }
2538                         DrawPosition(FALSE, NULL);
2539                         DisplayTitle(str);
2540                     }
2541                     /* Suppress following prompt */
2542                     if (looking_at(buf, &i, "*% ")) {
2543                         savingComment = FALSE;
2544                     }
2545                     next_out = i;
2546                 }
2547                 continue;
2548             }
2549
2550             i++;                /* skip unparsed character and loop back */
2551         }
2552         
2553         if (started != STARTED_MOVES && started != STARTED_BOARD &&
2554             started != STARTED_HOLDINGS && i > next_out) {
2555             SendToPlayer(&buf[next_out], i - next_out);
2556             next_out = i;
2557         }
2558         
2559         leftover_len = buf_len - leftover_start;
2560         /* if buffer ends with something we couldn't parse,
2561            reparse it after appending the next read */
2562         
2563     } else if (count == 0) {
2564         RemoveInputSource(isr);
2565         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2566     } else {
2567         DisplayFatalError(_("Error reading from ICS"), error, 1);
2568     }
2569 }
2570
2571
2572 /* Board style 12 looks like this:
2573    
2574    <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
2575    
2576  * The "<12> " is stripped before it gets to this routine.  The two
2577  * trailing 0's (flip state and clock ticking) are later addition, and
2578  * some chess servers may not have them, or may have only the first.
2579  * Additional trailing fields may be added in the future.  
2580  */
2581
2582 #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"
2583
2584 #define RELATION_OBSERVING_PLAYED    0
2585 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
2586 #define RELATION_PLAYING_MYMOVE      1
2587 #define RELATION_PLAYING_NOTMYMOVE  -1
2588 #define RELATION_EXAMINING           2
2589 #define RELATION_ISOLATED_BOARD     -3
2590 #define RELATION_STARTING_POSITION  -4   /* FICS only */
2591
2592 void
2593 ParseBoard12(string)
2594      char *string;
2595
2596     GameMode newGameMode;
2597     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2598     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2599     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2600     char to_play, board_chars[72];
2601     char move_str[500], str[500], elapsed_time[500];
2602     char black[32], white[32];
2603     Board board;
2604     int prevMove = currentMove;
2605     int ticking = 2;
2606     ChessMove moveType;
2607     int fromX, fromY, toX, toY;
2608     char promoChar;
2609
2610     fromX = fromY = toX = toY = -1;
2611     
2612     newGame = FALSE;
2613
2614     if (appData.debugMode)
2615       fprintf(debugFP, _("Parsing board: %s\n"), string);
2616
2617     move_str[0] = NULLCHAR;
2618     elapsed_time[0] = NULLCHAR;
2619     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2620                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2621                &gamenum, white, black, &relation, &basetime, &increment,
2622                &white_stren, &black_stren, &white_time, &black_time,
2623                &moveNum, str, elapsed_time, move_str, &ics_flip,
2624                &ticking);
2625
2626     if (n < 22) {
2627         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2628         DisplayError(str, 0);
2629         return;
2630     }
2631
2632     /* Convert the move number to internal form */
2633     moveNum = (moveNum - 1) * 2;
2634     if (to_play == 'B') moveNum++;
2635     if (moveNum >= MAX_MOVES) {
2636       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2637                         0, 1);
2638       return;
2639     }
2640     
2641     switch (relation) {
2642       case RELATION_OBSERVING_PLAYED:
2643       case RELATION_OBSERVING_STATIC:
2644         if (gamenum == -1) {
2645             /* Old ICC buglet */
2646             relation = RELATION_OBSERVING_STATIC;
2647         }
2648         newGameMode = IcsObserving;
2649         break;
2650       case RELATION_PLAYING_MYMOVE:
2651       case RELATION_PLAYING_NOTMYMOVE:
2652         newGameMode =
2653           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2654             IcsPlayingWhite : IcsPlayingBlack;
2655         break;
2656       case RELATION_EXAMINING:
2657         newGameMode = IcsExamining;
2658         break;
2659       case RELATION_ISOLATED_BOARD:
2660       default:
2661         /* Just display this board.  If user was doing something else,
2662            we will forget about it until the next board comes. */ 
2663         newGameMode = IcsIdle;
2664         break;
2665       case RELATION_STARTING_POSITION:
2666         newGameMode = gameMode;
2667         break;
2668     }
2669     
2670     /* Modify behavior for initial board display on move listing
2671        of wild games.
2672        */
2673     switch (ics_getting_history) {
2674       case H_FALSE:
2675       case H_REQUESTED:
2676         break;
2677       case H_GOT_REQ_HEADER:
2678       case H_GOT_UNREQ_HEADER:
2679         /* This is the initial position of the current game */
2680         gamenum = ics_gamenum;
2681         moveNum = 0;            /* old ICS bug workaround */
2682         if (to_play == 'B') {
2683           startedFromSetupPosition = TRUE;
2684           blackPlaysFirst = TRUE;
2685           moveNum = 1;
2686           if (forwardMostMove == 0) forwardMostMove = 1;
2687           if (backwardMostMove == 0) backwardMostMove = 1;
2688           if (currentMove == 0) currentMove = 1;
2689         }
2690         newGameMode = gameMode;
2691         relation = RELATION_STARTING_POSITION; /* ICC needs this */
2692         break;
2693       case H_GOT_UNWANTED_HEADER:
2694         /* This is an initial board that we don't want */
2695         return;
2696       case H_GETTING_MOVES:
2697         /* Should not happen */
2698         DisplayError(_("Error gathering move list: extra board"), 0);
2699         ics_getting_history = H_FALSE;
2700         return;
2701     }
2702     
2703     /* Take action if this is the first board of a new game, or of a
2704        different game than is currently being displayed.  */
2705     if (gamenum != ics_gamenum || newGameMode != gameMode ||
2706         relation == RELATION_ISOLATED_BOARD) {
2707         
2708         /* Forget the old game and get the history (if any) of the new one */
2709         if (gameMode != BeginningOfGame) {
2710           Reset(FALSE, TRUE);
2711         }
2712         newGame = TRUE;
2713         if (appData.autoRaiseBoard) BoardToTop();
2714         prevMove = -3;
2715         if (gamenum == -1) {
2716             newGameMode = IcsIdle;
2717         } else if (moveNum > 0 && newGameMode != IcsIdle &&
2718                    appData.getMoveList) {
2719             /* Need to get game history */
2720             ics_getting_history = H_REQUESTED;
2721             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2722             SendToICS(str);
2723         }
2724         
2725         /* Initially flip the board to have black on the bottom if playing
2726            black or if the ICS flip flag is set, but let the user change
2727            it with the Flip View button. */
2728         flipView = appData.autoFlipView ? 
2729           (newGameMode == IcsPlayingBlack) || ics_flip :
2730           appData.flipView;
2731         
2732         /* Done with values from previous mode; copy in new ones */
2733         gameMode = newGameMode;
2734         ModeHighlight();
2735         ics_gamenum = gamenum;
2736         if (gamenum == gs_gamenum) {
2737             int klen = strlen(gs_kind);
2738             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2739             sprintf(str, "ICS %s", gs_kind);
2740             gameInfo.event = StrSave(str);
2741         } else {
2742             gameInfo.event = StrSave("ICS game");
2743         }
2744         gameInfo.site = StrSave(appData.icsHost);
2745         gameInfo.date = PGNDate();
2746         gameInfo.round = StrSave("-");
2747         gameInfo.white = StrSave(white);
2748         gameInfo.black = StrSave(black);
2749         timeControl = basetime * 60 * 1000;
2750         timeIncrement = increment * 1000;
2751         movesPerSession = 0;
2752         gameInfo.timeControl = TimeControlTagValue();
2753         gameInfo.variant = StringToVariant(gameInfo.event);
2754         
2755         /* Do we have the ratings? */
2756         if (strcmp(player1Name, white) == 0 &&
2757             strcmp(player2Name, black) == 0) {
2758             if (appData.debugMode)
2759               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2760                       player1Rating, player2Rating);
2761             gameInfo.whiteRating = player1Rating;
2762             gameInfo.blackRating = player2Rating;
2763         } else if (strcmp(player2Name, white) == 0 &&
2764                    strcmp(player1Name, black) == 0) {
2765             if (appData.debugMode)
2766               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2767                       player2Rating, player1Rating);
2768             gameInfo.whiteRating = player2Rating;
2769             gameInfo.blackRating = player1Rating;
2770         }
2771         player1Name[0] = player2Name[0] = NULLCHAR;
2772
2773         /* Silence shouts if requested */
2774         if (appData.quietPlay &&
2775             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2776             SendToICS(ics_prefix);
2777             SendToICS("set shout 0\n");
2778         }
2779     }
2780     
2781     /* Deal with midgame name changes */
2782     if (!newGame) {
2783         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2784             if (gameInfo.white) free(gameInfo.white);
2785             gameInfo.white = StrSave(white);
2786         }
2787         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2788             if (gameInfo.black) free(gameInfo.black);
2789             gameInfo.black = StrSave(black);
2790         }
2791     }
2792     
2793     /* Throw away game result if anything actually changes in examine mode */
2794     if (gameMode == IcsExamining && !newGame) {
2795         gameInfo.result = GameUnfinished;
2796         if (gameInfo.resultDetails != NULL) {
2797             free(gameInfo.resultDetails);
2798             gameInfo.resultDetails = NULL;
2799         }
2800     }
2801     
2802     /* In pausing && IcsExamining mode, we ignore boards coming
2803        in if they are in a different variation than we are. */
2804     if (pauseExamInvalid) return;
2805     if (pausing && gameMode == IcsExamining) {
2806         if (moveNum <= pauseExamForwardMostMove) {
2807             pauseExamInvalid = TRUE;
2808             forwardMostMove = pauseExamForwardMostMove;
2809             return;
2810         }
2811     }
2812     
2813     /* Parse the board */
2814     for (k = 0; k < 8; k++)
2815       for (j = 0; j < 8; j++)
2816         board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2817     CopyBoard(boards[moveNum], board);
2818     if (moveNum == 0) {
2819         startedFromSetupPosition =
2820           !CompareBoards(board, initialPosition);
2821     }
2822     
2823     if (ics_getting_history == H_GOT_REQ_HEADER ||
2824         ics_getting_history == H_GOT_UNREQ_HEADER) {
2825         /* This was an initial position from a move list, not
2826            the current position */
2827         return;
2828     }
2829     
2830     /* Update currentMove and known move number limits */
2831     newMove = newGame || moveNum > forwardMostMove;
2832     if (newGame) {
2833         forwardMostMove = backwardMostMove = currentMove = moveNum;
2834         if (gameMode == IcsExamining && moveNum == 0) {
2835           /* Workaround for ICS limitation: we are not told the wild
2836              type when starting to examine a game.  But if we ask for
2837              the move list, the move list header will tell us */
2838             ics_getting_history = H_REQUESTED;
2839             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2840             SendToICS(str);
2841         }
2842     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2843                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2844         forwardMostMove = moveNum;
2845         if (!pausing || currentMove > forwardMostMove)
2846           currentMove = forwardMostMove;
2847     } else {
2848         /* New part of history that is not contiguous with old part */ 
2849         if (pausing && gameMode == IcsExamining) {
2850             pauseExamInvalid = TRUE;
2851             forwardMostMove = pauseExamForwardMostMove;
2852             return;
2853         }
2854         forwardMostMove = backwardMostMove = currentMove = moveNum;
2855         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2856             ics_getting_history = H_REQUESTED;
2857             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2858             SendToICS(str);
2859         }
2860     }
2861     
2862     /* Update the clocks */
2863     if (strchr(elapsed_time, '.')) {
2864       /* Time is in ms */
2865       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2866       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2867     } else {
2868       /* Time is in seconds */
2869       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2870       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2871     }
2872       
2873
2874 #if ZIPPY
2875     if (appData.zippyPlay && newGame &&
2876         gameMode != IcsObserving && gameMode != IcsIdle &&
2877         gameMode != IcsExamining)
2878       ZippyFirstBoard(moveNum, basetime, increment);
2879 #endif
2880     
2881     /* Put the move on the move list, first converting
2882        to canonical algebraic form. */
2883     if (moveNum > 0) {
2884         if (moveNum <= backwardMostMove) {
2885             /* We don't know what the board looked like before
2886                this move.  Punt. */
2887             strcpy(parseList[moveNum - 1], move_str);
2888             strcat(parseList[moveNum - 1], " ");
2889             strcat(parseList[moveNum - 1], elapsed_time);
2890             moveList[moveNum - 1][0] = NULLCHAR;
2891         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2892                                 &fromX, &fromY, &toX, &toY, &promoChar)) {
2893             (void) CoordsToAlgebraic(boards[moveNum - 1],
2894                                      PosFlags(moveNum - 1), EP_UNKNOWN,
2895                                      fromY, fromX, toY, toX, promoChar,
2896                                      parseList[moveNum-1]);
2897             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2898               case MT_NONE:
2899               case MT_STALEMATE:
2900               default:
2901                 break;
2902               case MT_CHECK:
2903                 strcat(parseList[moveNum - 1], "+");
2904                 break;
2905               case MT_CHECKMATE:
2906                 strcat(parseList[moveNum - 1], "#");
2907                 break;
2908             }
2909             strcat(parseList[moveNum - 1], " ");
2910             strcat(parseList[moveNum - 1], elapsed_time);
2911             /* currentMoveString is set as a side-effect of ParseOneMove */
2912             strcpy(moveList[moveNum - 1], currentMoveString);
2913             strcat(moveList[moveNum - 1], "\n");
2914         } else if (strcmp(move_str, "none") == 0) {
2915             /* Again, we don't know what the board looked like;
2916                this is really the start of the game. */
2917             parseList[moveNum - 1][0] = NULLCHAR;
2918             moveList[moveNum - 1][0] = NULLCHAR;
2919             backwardMostMove = moveNum;
2920             startedFromSetupPosition = TRUE;
2921             fromX = fromY = toX = toY = -1;
2922         } else {
2923             /* Move from ICS was illegal!?  Punt. */
2924 #if 0
2925             if (appData.testLegality && appData.debugMode) {
2926                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2927                 DisplayError(str, 0);
2928             }
2929 #endif
2930             strcpy(parseList[moveNum - 1], move_str);
2931             strcat(parseList[moveNum - 1], " ");
2932             strcat(parseList[moveNum - 1], elapsed_time);
2933             moveList[moveNum - 1][0] = NULLCHAR;
2934             fromX = fromY = toX = toY = -1;
2935         }
2936
2937 #if ZIPPY
2938         /* Send move to chess program (BEFORE animating it). */
2939         if (appData.zippyPlay && !newGame && newMove && 
2940            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2941
2942             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2943                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2944                 if (moveList[moveNum - 1][0] == NULLCHAR) {
2945                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2946                             move_str);
2947                     DisplayError(str, 0);
2948                 } else {
2949                     if (first.sendTime) {
2950                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2951                     }
2952                     SendMoveToProgram(moveNum - 1, &first);
2953                     if (firstMove) {
2954                         firstMove = FALSE;
2955                         if (first.useColors) {
2956                           SendToProgram(gameMode == IcsPlayingWhite ?
2957                                         "white\ngo\n" :
2958                                         "black\ngo\n", &first);
2959                         } else {
2960                           SendToProgram("go\n", &first);
2961                         }
2962                         first.maybeThinking = TRUE;
2963                     }
2964                 }
2965             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
2966               if (moveList[moveNum - 1][0] == NULLCHAR) {
2967                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
2968                 DisplayError(str, 0);
2969               } else {
2970                 SendMoveToProgram(moveNum - 1, &first);
2971               }
2972             }
2973         }
2974 #endif
2975     }
2976
2977     if (moveNum > 0 && !gotPremove) {
2978         /* If move comes from a remote source, animate it.  If it
2979            isn't remote, it will have already been animated. */
2980         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
2981             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
2982         }
2983         if (!pausing && appData.highlightLastMove) {
2984             SetHighlights(fromX, fromY, toX, toY);
2985         }
2986     }
2987     
2988     /* Start the clocks */
2989     whiteFlag = blackFlag = FALSE;
2990     appData.clockMode = !(basetime == 0 && increment == 0);
2991     if (ticking == 0) {
2992       ics_clock_paused = TRUE;
2993       StopClocks();
2994     } else if (ticking == 1) {
2995       ics_clock_paused = FALSE;
2996     }
2997     if (gameMode == IcsIdle ||
2998         relation == RELATION_OBSERVING_STATIC ||
2999         relation == RELATION_EXAMINING ||
3000         ics_clock_paused)
3001       DisplayBothClocks();
3002     else
3003       StartClocks();
3004     
3005     /* Display opponents and material strengths */
3006     if (gameInfo.variant != VariantBughouse &&
3007         gameInfo.variant != VariantCrazyhouse) {
3008         if (tinyLayout || smallLayout) {
3009             sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3010                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3011                     basetime, increment);
3012         } else {
3013             sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3014                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3015                     basetime, increment);
3016         }
3017         DisplayTitle(str);
3018     }
3019
3020    
3021     /* Display the board */
3022     if (!pausing) {
3023       
3024       if (appData.premove)
3025           if (!gotPremove || 
3026              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3027              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3028               ClearPremoveHighlights();
3029
3030       DrawPosition(FALSE, boards[currentMove]);
3031       DisplayMove(moveNum - 1);
3032       if (appData.ringBellAfterMoves && !ics_user_moved)
3033         RingBell();
3034     }
3035
3036     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3037 }
3038
3039 void
3040 GetMoveListEvent()
3041 {
3042     char buf[MSG_SIZ];
3043     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3044         ics_getting_history = H_REQUESTED;
3045         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3046         SendToICS(buf);
3047     }
3048 }
3049
3050 void
3051 AnalysisPeriodicEvent(force)
3052      int force;
3053 {
3054     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3055          && !force) || !appData.periodicUpdates)
3056       return;
3057
3058     /* Send . command to Crafty to collect stats */
3059     SendToProgram(".\n", &first);
3060
3061     /* Don't send another until we get a response (this makes
3062        us stop sending to old Crafty's which don't understand
3063        the "." command (sending illegal cmds resets node count & time,
3064        which looks bad)) */
3065     programStats.ok_to_send = 0;
3066 }
3067
3068 void
3069 SendMoveToProgram(moveNum, cps)
3070      int moveNum;
3071      ChessProgramState *cps;
3072 {
3073     char buf[MSG_SIZ];
3074     if (cps->useUsermove) {
3075       SendToProgram("usermove ", cps);
3076     }
3077     if (cps->useSAN) {
3078       char *space;
3079       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3080         int len = space - parseList[moveNum];
3081         memcpy(buf, parseList[moveNum], len);
3082         buf[len++] = '\n';
3083         buf[len] = NULLCHAR;
3084       } else {
3085         sprintf(buf, "%s\n", parseList[moveNum]);
3086       }
3087       SendToProgram(buf, cps);
3088     } else {
3089       SendToProgram(moveList[moveNum], cps);
3090     }
3091 }
3092
3093 void
3094 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3095      ChessMove moveType;
3096      int fromX, fromY, toX, toY;
3097 {
3098     char user_move[MSG_SIZ];
3099
3100     switch (moveType) {
3101       default:
3102         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3103                 (int)moveType, fromX, fromY, toX, toY);
3104         DisplayError(user_move + strlen("say "), 0);
3105         break;
3106       case WhiteKingSideCastle:
3107       case BlackKingSideCastle:
3108       case WhiteQueenSideCastleWild:
3109       case BlackQueenSideCastleWild:
3110         sprintf(user_move, "o-o\n");
3111         break;
3112       case WhiteQueenSideCastle:
3113       case BlackQueenSideCastle:
3114       case WhiteKingSideCastleWild:
3115       case BlackKingSideCastleWild:
3116         sprintf(user_move, "o-o-o\n");
3117         break;
3118       case WhitePromotionQueen:
3119       case BlackPromotionQueen:
3120       case WhitePromotionRook:
3121       case BlackPromotionRook:
3122       case WhitePromotionBishop:
3123       case BlackPromotionBishop:
3124       case WhitePromotionKnight:
3125       case BlackPromotionKnight:
3126       case WhitePromotionKing:
3127       case BlackPromotionKing:
3128         sprintf(user_move, "%c%c%c%c=%c\n",
3129                 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3130                 PieceToChar(PromoPiece(moveType)));
3131         break;
3132       case WhiteDrop:
3133       case BlackDrop:
3134         sprintf(user_move, "%c@%c%c\n",
3135                 ToUpper(PieceToChar((ChessSquare) fromX)),
3136                 'a' + toX, '1' + toY);
3137         break;
3138       case NormalMove:
3139       case WhiteCapturesEnPassant:
3140       case BlackCapturesEnPassant:
3141       case IllegalMove:  /* could be a variant we don't quite understand */
3142         sprintf(user_move, "%c%c%c%c\n",
3143                 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3144         break;
3145     }
3146     SendToICS(user_move);
3147 }
3148
3149 void
3150 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3151      int rf, ff, rt, ft;
3152      char promoChar;
3153      char move[7];
3154 {
3155     if (rf == DROP_RANK) {
3156         sprintf(move, "%c@%c%c\n",
3157                 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3158     } else {
3159         if (promoChar == 'x' || promoChar == NULLCHAR) {
3160             sprintf(move, "%c%c%c%c\n",
3161                     'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3162         } else {
3163             sprintf(move, "%c%c%c%c%c\n",
3164                     'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3165         }
3166     }
3167 }
3168
3169 void
3170 ProcessICSInitScript(f)
3171      FILE *f;
3172 {
3173     char buf[MSG_SIZ];
3174
3175     while (fgets(buf, MSG_SIZ, f)) {
3176         SendToICSDelayed(buf,(long)appData.msLoginDelay);
3177     }
3178
3179     fclose(f);
3180 }
3181
3182
3183 /* Parser for moves from gnuchess, ICS, or user typein box */
3184 Boolean
3185 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3186      char *move;
3187      int moveNum;
3188      ChessMove *moveType;
3189      int *fromX, *fromY, *toX, *toY;
3190      char *promoChar;
3191 {       
3192     *moveType = yylexstr(moveNum, move);
3193     switch (*moveType) {
3194       case WhitePromotionQueen:
3195       case BlackPromotionQueen:
3196       case WhitePromotionRook:
3197       case BlackPromotionRook:
3198       case WhitePromotionBishop:
3199       case BlackPromotionBishop:
3200       case WhitePromotionKnight:
3201       case BlackPromotionKnight:
3202       case WhitePromotionKing:
3203       case BlackPromotionKing:
3204       case NormalMove:
3205       case WhiteCapturesEnPassant:
3206       case BlackCapturesEnPassant:
3207       case WhiteKingSideCastle:
3208       case WhiteQueenSideCastle:
3209       case BlackKingSideCastle:
3210       case BlackQueenSideCastle:
3211       case WhiteKingSideCastleWild:
3212       case WhiteQueenSideCastleWild:
3213       case BlackKingSideCastleWild:
3214       case BlackQueenSideCastleWild:
3215       case IllegalMove:         /* bug or odd chess variant */
3216         *fromX = currentMoveString[0] - 'a';
3217         *fromY = currentMoveString[1] - '1';
3218         *toX = currentMoveString[2] - 'a';
3219         *toY = currentMoveString[3] - '1';
3220         *promoChar = currentMoveString[4];
3221         if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3222             *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3223             *fromX = *fromY = *toX = *toY = 0;
3224             return FALSE;
3225         }
3226         if (appData.testLegality) {
3227           return (*moveType != IllegalMove);
3228         } else {
3229           return !(fromX == fromY && toX == toY);
3230         }
3231
3232       case WhiteDrop:
3233       case BlackDrop:
3234         *fromX = *moveType == WhiteDrop ?
3235           (int) CharToPiece(ToUpper(currentMoveString[0])) :
3236         (int) CharToPiece(ToLower(currentMoveString[0]));
3237         *fromY = DROP_RANK;
3238         *toX = currentMoveString[2] - 'a';
3239         *toY = currentMoveString[3] - '1';
3240         *promoChar = NULLCHAR;
3241         return TRUE;
3242
3243       case AmbiguousMove:
3244       case ImpossibleMove:
3245       case (ChessMove) 0:       /* end of file */
3246       case ElapsedTime:
3247       case Comment:
3248       case PGNTag:
3249       case NAG:
3250       case WhiteWins:
3251       case BlackWins:
3252       case GameIsDrawn:
3253       default:
3254         /* bug? */
3255         *fromX = *fromY = *toX = *toY = 0;
3256         *promoChar = NULLCHAR;
3257         return FALSE;
3258     }
3259 }
3260
3261
3262 void
3263 InitPosition(redraw)
3264      int redraw;
3265 {
3266     currentMove = forwardMostMove = backwardMostMove = 0;
3267     switch (gameInfo.variant) {
3268     default:
3269       CopyBoard(boards[0], initialPosition);
3270       break;
3271     case VariantTwoKings:
3272       CopyBoard(boards[0], twoKingsPosition);
3273       startedFromSetupPosition = TRUE;
3274       break;
3275     case VariantWildCastle:
3276       CopyBoard(boards[0], initialPosition);
3277       /* !!?shuffle with kings guaranteed to be on d or e file */
3278       break;
3279     case VariantNoCastle:
3280       CopyBoard(boards[0], initialPosition);
3281       /* !!?unconstrained back-rank shuffle */
3282       break;
3283     case VariantFischeRandom:
3284       CopyBoard(boards[0], initialPosition);
3285       /* !!shuffle according to FR rules */
3286       break;
3287     }
3288     if (redraw)
3289       DrawPosition(FALSE, boards[currentMove]);
3290 }
3291
3292 void
3293 SendBoard(cps, moveNum)
3294      ChessProgramState *cps;
3295      int moveNum;
3296 {
3297     char message[MSG_SIZ];
3298     
3299     if (cps->useSetboard) {
3300       char* fen = PositionToFEN(moveNum);
3301       sprintf(message, "setboard %s\n", fen);
3302       SendToProgram(message, cps);
3303       free(fen);
3304
3305     } else {
3306       ChessSquare *bp;
3307       int i, j;
3308       /* Kludge to set black to move, avoiding the troublesome and now
3309        * deprecated "black" command.
3310        */
3311       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3312
3313       SendToProgram("edit\n", cps);
3314       SendToProgram("#\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) BlackPawn) {
3319             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
3320                     'a' + j, '1' + i);
3321             SendToProgram(message, cps);
3322           }
3323         }
3324       }
3325     
3326       SendToProgram("c\n", cps);
3327       for (i = BOARD_SIZE - 1; i >= 0; i--) {
3328         bp = &boards[moveNum][i][0];
3329         for (j = 0; j < BOARD_SIZE; j++, bp++) {
3330           if (((int) *bp != (int) EmptySquare)
3331               && ((int) *bp >= (int) BlackPawn)) {
3332             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3333                     'a' + j, '1' + i);
3334             SendToProgram(message, cps);
3335           }
3336         }
3337       }
3338     
3339       SendToProgram(".\n", cps);
3340     }
3341 }
3342
3343 int
3344 IsPromotion(fromX, fromY, toX, toY)
3345      int fromX, fromY, toX, toY;
3346 {
3347     return gameMode != EditPosition &&
3348       fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3349         ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3350          (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3351 }
3352
3353
3354 int
3355 PieceForSquare (x, y)
3356      int x;
3357      int y;
3358 {
3359   if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3360      return -1;
3361   else
3362      return boards[currentMove][y][x];
3363 }
3364
3365 int
3366 OKToStartUserMove(x, y)
3367      int x, y;
3368 {
3369     ChessSquare from_piece;
3370     int white_piece;
3371
3372     if (matchMode) return FALSE;
3373     if (gameMode == EditPosition) return TRUE;
3374
3375     if (x >= 0 && y >= 0)
3376       from_piece = boards[currentMove][y][x];
3377     else
3378       from_piece = EmptySquare;
3379
3380     if (from_piece == EmptySquare) return FALSE;
3381
3382     white_piece = (int)from_piece >= (int)WhitePawn &&
3383       (int)from_piece <= (int)WhiteKing;
3384
3385     switch (gameMode) {
3386       case PlayFromGameFile:
3387       case AnalyzeFile:
3388       case TwoMachinesPlay:
3389       case EndOfGame:
3390         return FALSE;
3391
3392       case IcsObserving:
3393       case IcsIdle:
3394         return FALSE;
3395
3396       case MachinePlaysWhite:
3397       case IcsPlayingBlack:
3398         if (appData.zippyPlay) return FALSE;
3399         if (white_piece) {
3400             DisplayMoveError(_("You are playing Black"));
3401             return FALSE;
3402         }
3403         break;
3404
3405       case MachinePlaysBlack:
3406       case IcsPlayingWhite:
3407         if (appData.zippyPlay) return FALSE;
3408         if (!white_piece) {
3409             DisplayMoveError(_("You are playing White"));
3410             return FALSE;
3411         }
3412         break;
3413
3414       case EditGame:
3415         if (!white_piece && WhiteOnMove(currentMove)) {
3416             DisplayMoveError(_("It is White's turn"));
3417             return FALSE;
3418         }           
3419         if (white_piece && !WhiteOnMove(currentMove)) {
3420             DisplayMoveError(_("It is Black's turn"));
3421             return FALSE;
3422         }           
3423         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3424             /* Editing correspondence game history */
3425             /* Could disallow this or prompt for confirmation */
3426             cmailOldMove = -1;
3427         }
3428         if (currentMove < forwardMostMove) {
3429             /* Discarding moves */
3430             /* Could prompt for confirmation here,
3431                but I don't think that's such a good idea */
3432             forwardMostMove = currentMove;
3433         }
3434         break;
3435
3436       case BeginningOfGame:
3437         if (appData.icsActive) return FALSE;
3438         if (!appData.noChessProgram) {
3439             if (!white_piece) {
3440                 DisplayMoveError(_("You are playing White"));
3441                 return FALSE;
3442             }
3443         }
3444         break;
3445         
3446       case Training:
3447         if (!white_piece && WhiteOnMove(currentMove)) {
3448             DisplayMoveError(_("It is White's turn"));
3449             return FALSE;
3450         }           
3451         if (white_piece && !WhiteOnMove(currentMove)) {
3452             DisplayMoveError(_("It is Black's turn"));
3453             return FALSE;
3454         }           
3455         break;
3456
3457       default:
3458       case IcsExamining:
3459         break;
3460     }
3461     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3462         && gameMode != AnalyzeFile && gameMode != Training) {
3463         DisplayMoveError(_("Displayed position is not current"));
3464         return FALSE;
3465     }
3466     return TRUE;
3467 }
3468
3469 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3470 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3471 int lastLoadGameUseList = FALSE;
3472 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3473 ChessMove lastLoadGameStart = (ChessMove) 0;
3474
3475
3476 void
3477 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3478      int fromX, fromY, toX, toY;
3479      int promoChar;
3480 {
3481     ChessMove moveType;
3482
3483     if (fromX < 0 || fromY < 0) return;
3484     if ((fromX == toX) && (fromY == toY)) {
3485         return;
3486     }
3487         
3488     /* Check if the user is playing in turn.  This is complicated because we
3489        let the user "pick up" a piece before it is his turn.  So the piece he
3490        tried to pick up may have been captured by the time he puts it down!
3491        Therefore we use the color the user is supposed to be playing in this
3492        test, not the color of the piece that is currently on the starting
3493        square---except in EditGame mode, where the user is playing both
3494        sides; fortunately there the capture race can't happen.  (It can
3495        now happen in IcsExamining mode, but that's just too bad.  The user
3496        will get a somewhat confusing message in that case.)
3497        */
3498
3499     switch (gameMode) {
3500       case PlayFromGameFile:
3501       case AnalyzeFile:
3502       case TwoMachinesPlay:
3503       case EndOfGame:
3504       case IcsObserving:
3505       case IcsIdle:
3506         /* We switched into a game mode where moves are not accepted,
3507            perhaps while the mouse button was down. */
3508         return;
3509
3510       case MachinePlaysWhite:
3511         /* User is moving for Black */
3512         if (WhiteOnMove(currentMove)) {
3513             DisplayMoveError(_("It is White's turn"));
3514             return;
3515         }
3516         break;
3517
3518       case MachinePlaysBlack:
3519         /* User is moving for White */
3520         if (!WhiteOnMove(currentMove)) {
3521             DisplayMoveError(_("It is Black's turn"));
3522             return;
3523         }
3524         break;
3525
3526       case EditGame:
3527       case IcsExamining:
3528       case BeginningOfGame:
3529       case AnalyzeMode:
3530       case Training:
3531         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3532             (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3533             /* User is moving for Black */
3534             if (WhiteOnMove(currentMove)) {
3535                 DisplayMoveError(_("It is White's turn"));
3536                 return;
3537             }
3538         } else {
3539             /* User is moving for White */
3540             if (!WhiteOnMove(currentMove)) {
3541                 DisplayMoveError(_("It is Black's turn"));
3542                 return;
3543             }
3544         }
3545         break;
3546
3547       case IcsPlayingBlack:
3548         /* User is moving for Black */
3549         if (WhiteOnMove(currentMove)) {
3550             if (!appData.premove) {
3551                 DisplayMoveError(_("It is White's turn"));
3552             } else if (toX >= 0 && toY >= 0) {
3553                 premoveToX = toX;
3554                 premoveToY = toY;
3555                 premoveFromX = fromX;
3556                 premoveFromY = fromY;
3557                 premovePromoChar = promoChar;
3558                 gotPremove = 1;
3559                 if (appData.debugMode) 
3560                     fprintf(debugFP, "Got premove: fromX %d,"
3561                             "fromY %d, toX %d, toY %d\n",
3562                             fromX, fromY, toX, toY);
3563             }
3564             return;
3565         }
3566         break;
3567
3568       case IcsPlayingWhite:
3569         /* User is moving for White */
3570         if (!WhiteOnMove(currentMove)) {
3571             if (!appData.premove) {
3572                 DisplayMoveError(_("It is Black's turn"));
3573             } else if (toX >= 0 && toY >= 0) {
3574                 premoveToX = toX;
3575                 premoveToY = toY;
3576                 premoveFromX = fromX;
3577                 premoveFromY = fromY;
3578                 premovePromoChar = promoChar;
3579                 gotPremove = 1;
3580                 if (appData.debugMode) 
3581                     fprintf(debugFP, "Got premove: fromX %d,"
3582                             "fromY %d, toX %d, toY %d\n",
3583                             fromX, fromY, toX, toY);
3584             }
3585             return;
3586         }
3587         break;
3588
3589       default:
3590         break;
3591
3592       case EditPosition:
3593         if (toX == -2 || toY == -2) {
3594             boards[0][fromY][fromX] = EmptySquare;
3595             DrawPosition(FALSE, boards[currentMove]);
3596         } else if (toX >= 0 && toY >= 0) {
3597             boards[0][toY][toX] = boards[0][fromY][fromX];
3598             boards[0][fromY][fromX] = EmptySquare;
3599             DrawPosition(FALSE, boards[currentMove]);
3600         }
3601         return;
3602     }
3603
3604     if (toX < 0 || toY < 0) return;
3605     userOfferedDraw = FALSE;
3606         
3607     if (appData.testLegality) {
3608         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3609                                 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3610         if (moveType == IllegalMove || moveType == ImpossibleMove) {
3611             DisplayMoveError(_("Illegal move"));
3612             return;
3613         }
3614     } else {
3615         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3616     }
3617
3618     if (gameMode == Training) {
3619       /* compare the move played on the board to the next move in the
3620        * game. If they match, display the move and the opponent's response. 
3621        * If they don't match, display an error message.
3622        */
3623       int saveAnimate;
3624       Board testBoard;
3625       CopyBoard(testBoard, boards[currentMove]);
3626       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3627
3628       if (CompareBoards(testBoard, boards[currentMove+1])) {
3629         ForwardInner(currentMove+1);
3630
3631         /* Autoplay the opponent's response.
3632          * if appData.animate was TRUE when Training mode was entered,
3633          * the response will be animated.
3634          */
3635         saveAnimate = appData.animate;
3636         appData.animate = animateTraining;
3637         ForwardInner(currentMove+1);
3638         appData.animate = saveAnimate;
3639
3640         /* check for the end of the game */
3641         if (currentMove >= forwardMostMove) {
3642           gameMode = PlayFromGameFile;
3643           ModeHighlight();
3644           SetTrainingModeOff();
3645           DisplayInformation(_("End of game"));
3646         }
3647       } else {
3648         DisplayError(_("Incorrect move"), 0);
3649       }
3650       return;
3651     }
3652
3653     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3654 }
3655
3656 /* Common tail of UserMoveEvent and DropMenuEvent */
3657 void
3658 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3659      ChessMove moveType;
3660      int fromX, fromY, toX, toY;
3661      /*char*/int promoChar;
3662 {
3663   /* Ok, now we know that the move is good, so we can kill
3664      the previous line in Analysis Mode */
3665   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3666     forwardMostMove = currentMove;
3667   }
3668
3669   /* If we need the chess program but it's dead, restart it */
3670   ResurrectChessProgram();
3671
3672   /* A user move restarts a paused game*/
3673   if (pausing)
3674     PauseEvent();
3675
3676   thinkOutput[0] = NULLCHAR;
3677
3678   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3679
3680   if (gameMode == BeginningOfGame) {
3681     if (appData.noChessProgram) {
3682       gameMode = EditGame;
3683       SetGameInfo();
3684     } else {
3685       char buf[MSG_SIZ];
3686       gameMode = MachinePlaysBlack;
3687       SetGameInfo();
3688       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3689       DisplayTitle(buf);
3690       if (first.sendName) {
3691         sprintf(buf, "name %s\n", gameInfo.white);
3692         SendToProgram(buf, &first);
3693       }
3694     }
3695     ModeHighlight();
3696   }
3697
3698   /* Relay move to ICS or chess engine */
3699   if (appData.icsActive) {
3700     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3701         gameMode == IcsExamining) {
3702       SendMoveToICS(moveType, fromX, fromY, toX, toY);
3703       ics_user_moved = 1;
3704     }
3705   } else {
3706     if (first.sendTime && (gameMode == BeginningOfGame ||
3707                            gameMode == MachinePlaysWhite ||
3708                            gameMode == MachinePlaysBlack)) {
3709       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3710     }
3711     SendMoveToProgram(forwardMostMove-1, &first);
3712     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3713       first.maybeThinking = TRUE;
3714     }
3715     if (currentMove == cmailOldMove + 1) {
3716       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3717     }
3718   }
3719
3720   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3721
3722   switch (gameMode) {
3723   case EditGame:
3724     switch (MateTest(boards[currentMove], PosFlags(currentMove),
3725                      EP_UNKNOWN)) {
3726     case MT_NONE:
3727     case MT_CHECK:
3728       break;
3729     case MT_CHECKMATE:
3730       if (WhiteOnMove(currentMove)) {
3731         GameEnds(BlackWins, "Black mates", GE_PLAYER);
3732       } else {
3733         GameEnds(WhiteWins, "White mates", GE_PLAYER);
3734       }
3735       break;
3736     case MT_STALEMATE:
3737       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3738       break;
3739     }
3740     break;
3741     
3742   case MachinePlaysBlack:
3743   case MachinePlaysWhite:
3744     /* disable certain menu options while machine is thinking */
3745     SetMachineThinkingEnables();
3746     break;
3747
3748   default:
3749     break;
3750   }
3751 }
3752
3753 void
3754 HandleMachineMove(message, cps)
3755      char *message;
3756      ChessProgramState *cps;
3757 {
3758     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3759     char realname[MSG_SIZ];
3760     int fromX, fromY, toX, toY;
3761     ChessMove moveType;
3762     char promoChar;
3763     char *p;
3764     int machineWhite;
3765
3766     /*
3767      * Kludge to ignore BEL characters
3768      */
3769     while (*message == '\007') message++;
3770
3771     /*
3772      * Look for book output
3773      */
3774     if (cps == &first && bookRequested) {
3775         if (message[0] == '\t' || message[0] == ' ') {
3776             /* Part of the book output is here; append it */
3777             strcat(bookOutput, message);
3778             strcat(bookOutput, "  \n");
3779             return;
3780         } else if (bookOutput[0] != NULLCHAR) {
3781             /* All of book output has arrived; display it */
3782             char *p = bookOutput;
3783             while (*p != NULLCHAR) {
3784                 if (*p == '\t') *p = ' ';
3785                 p++;
3786             }
3787             DisplayInformation(bookOutput);
3788             bookRequested = FALSE;
3789             /* Fall through to parse the current output */
3790         }
3791     }
3792
3793     /*
3794      * Look for machine move.
3795      */
3796     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3797          strcmp(buf2, "...") == 0) ||
3798         (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3799          strcmp(buf1, "move") == 0)) {
3800
3801         /* This method is only useful on engines that support ping */
3802         if (cps->lastPing != cps->lastPong) {
3803           if (gameMode == BeginningOfGame) {
3804             /* Extra move from before last new; ignore */
3805             if (appData.debugMode) {
3806                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3807             }
3808           } else {
3809             if (appData.debugMode) {
3810                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3811                         cps->which, gameMode);
3812             }
3813             SendToProgram("undo\n", cps);
3814           }
3815           return;
3816         }
3817
3818         switch (gameMode) {
3819           case BeginningOfGame:
3820             /* Extra move from before last reset; ignore */
3821             if (appData.debugMode) {
3822                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3823             }
3824             return;
3825
3826           case EndOfGame:
3827           case IcsIdle:
3828           default:
3829             /* Extra move after we tried to stop.  The mode test is
3830                not a reliable way of detecting this problem, but it's
3831                the best we can do on engines that don't support ping.
3832             */
3833             if (appData.debugMode) {
3834                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3835                         cps->which, gameMode);
3836             }
3837             SendToProgram("undo\n", cps);
3838             return;
3839
3840           case MachinePlaysWhite:
3841           case IcsPlayingWhite:
3842             machineWhite = TRUE;
3843             break;
3844
3845           case MachinePlaysBlack:
3846           case IcsPlayingBlack:
3847             machineWhite = FALSE;
3848             break;
3849
3850           case TwoMachinesPlay:
3851             machineWhite = (cps->twoMachinesColor[0] == 'w');
3852             break;
3853         }
3854         if (WhiteOnMove(forwardMostMove) != machineWhite) {
3855             if (appData.debugMode) {
3856                 fprintf(debugFP,
3857                         "Ignoring move out of turn by %s, gameMode %d"
3858                         ", forwardMost %d\n",
3859                         cps->which, gameMode, forwardMostMove);
3860             }
3861             return;
3862         }
3863
3864         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3865                               &fromX, &fromY, &toX, &toY, &promoChar)) {
3866             /* Machine move could not be parsed; ignore it. */
3867             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3868                     machineMove, cps->which);
3869             DisplayError(buf1, 0);
3870             if (gameMode == TwoMachinesPlay) {
3871               GameEnds(machineWhite ? BlackWins : WhiteWins,
3872                        "Forfeit due to illegal move", GE_XBOARD);
3873             }
3874             return;
3875         }
3876
3877         hintRequested = FALSE;
3878         lastHint[0] = NULLCHAR;
3879         bookRequested = FALSE;
3880         /* Program may be pondering now */
3881         cps->maybeThinking = TRUE;
3882         if (cps->sendTime == 2) cps->sendTime = 1;
3883         if (cps->offeredDraw) cps->offeredDraw--;
3884
3885 #if ZIPPY
3886         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3887             first.initDone) {
3888           SendMoveToICS(moveType, fromX, fromY, toX, toY);
3889           ics_user_moved = 1;
3890         }
3891 #endif
3892         /* currentMoveString is set as a side-effect of ParseOneMove */
3893         strcpy(machineMove, currentMoveString);
3894         strcat(machineMove, "\n");
3895         strcpy(moveList[forwardMostMove], machineMove);
3896     
3897         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3898     
3899         if (gameMode == TwoMachinesPlay) {
3900             if (cps->other->send