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