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