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