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