2 * backend.c -- Common back end for X and Windows NT versions of
5 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
6 * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
8 * The following terms apply to Digital Equipment Corporation's copyright
10 * ------------------------------------------------------------------------
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.
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
28 * ------------------------------------------------------------------------
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.
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.
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 * ------------------------------------------------------------------------
48 * See the file ChangeLog for a revision history. */
55 #include <sys/types.h>
62 #else /* not STDC_HEADERS */
65 # else /* not HAVE_STRING_H */
67 # endif /* not HAVE_STRING_H */
68 #endif /* not STDC_HEADERS */
71 # include <sys/fcntl.h>
72 #else /* not HAVE_SYS_FCNTL_H */
75 # endif /* HAVE_FCNTL_H */
76 #endif /* not HAVE_SYS_FCNTL_H */
78 #if TIME_WITH_SYS_TIME
79 # include <sys/time.h>
83 # include <sys/time.h>
89 #if defined(_amigados) && !defined(__GNUC__)
94 extern int gettimeofday(struct timeval *, struct timezone *);
102 #include "frontend.h"
109 #include "backendz.h"
113 # define _(s) gettext (s)
114 # define N_(s) gettext_noop (s)
121 /* A point in time */
123 long sec; /* Assuming this is >= 32 bits */
124 int ms; /* Assuming this is >= 16 bits */
127 /* Search stats from chessprogram */
129 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
130 int depth; /* Current search depth */
131 int nr_moves; /* Total nr of root moves */
132 int moves_left; /* Moves remaining to be searched */
133 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
134 unsigned long nodes; /* # of nodes searched */
135 int time; /* Search time (centiseconds) */
136 int score; /* Score (centipawns) */
137 int got_only_move; /* If last msg was "(only move)" */
138 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
139 int ok_to_send; /* handshaking between send & recv */
140 int line_is_book; /* 1 if movelist is book moves */
141 int seen_stat; /* 1 if we've seen the stat01: line */
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,
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,
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));
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));
216 extern int tinyLayout, smallLayout;
217 static ChessProgramStats programStats;
219 /* States for ics_getting_history */
221 #define H_REQUESTED 1
222 #define H_GOT_REQ_HEADER 2
223 #define H_GOT_UNREQ_HEADER 3
224 #define H_GETTING_MOVES 4
225 #define H_GOT_UNWANTED_HEADER 5
227 /* whosays values for GameEnds */
234 /* Maximum number of games in a cmail message */
235 #define CMAIL_MAX_GAMES 20
237 /* Different types of move when calling RegisterMove */
239 #define CMAIL_RESIGN 1
241 #define CMAIL_ACCEPT 3
243 /* Different types of result to remember for each game */
244 #define CMAIL_NOT_RESULT 0
245 #define CMAIL_OLD_RESULT 1
246 #define CMAIL_NEW_RESULT 2
248 /* Telnet protocol constants */
258 /* Fake up flags for now, as we aren't keeping track of castling
263 int flags = F_ALL_CASTLE_OK;
264 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
265 switch (gameInfo.variant) {
267 case VariantGiveaway:
268 flags |= F_IGNORE_CHECK;
269 flags &= ~F_ALL_CASTLE_OK;
272 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
274 case VariantKriegspiel:
275 flags |= F_KRIEGSPIEL_CAPTURE;
277 case VariantNoCastle:
278 flags &= ~F_ALL_CASTLE_OK;
286 FILE *gameFileFP, *debugFP;
288 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
289 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
290 char thinkOutput1[MSG_SIZ*10];
292 ChessProgramState first, second;
294 /* premove variables */
297 int premoveFromX = 0;
298 int premoveFromY = 0;
299 int premovePromoChar = 0;
301 Boolean alarmSounded;
302 /* end premove variables */
304 #define ICS_GENERIC 0
307 #define ICS_CHESSNET 3 /* not really supported */
308 int ics_type = ICS_GENERIC;
309 char *ics_prefix = "$";
311 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
312 int pauseExamForwardMostMove = 0;
313 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
314 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
315 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
316 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
317 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
318 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
319 int whiteFlag = FALSE, blackFlag = FALSE;
320 int userOfferedDraw = FALSE;
321 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
322 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
323 int cmailMoveType[CMAIL_MAX_GAMES];
324 long ics_clock_paused = 0;
325 ProcRef icsPR = NoProc, cmailPR = NoProc;
326 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
327 GameMode gameMode = BeginningOfGame;
328 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
329 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
330 char white_holding[64], black_holding[64];
331 TimeMark lastNodeCountTime;
332 long lastNodeCount=0;
333 int have_sent_ICS_logon = 0;
335 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
336 long timeRemaining[2][MAX_MOVES];
338 TimeMark programStartTime;
339 char ics_handle[MSG_SIZ];
340 int have_set_title = 0;
342 /* animateTraining preserves the state of appData.animate
343 * when Training mode is activated. This allows the
344 * response to be animated when appData.animate == TRUE and
345 * appData.animateDragging == TRUE.
347 Boolean animateTraining;
353 Board boards[MAX_MOVES];
354 Board initialPosition = {
355 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
356 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
357 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
358 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
359 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
360 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
361 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
362 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
363 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
364 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
365 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
366 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
367 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
368 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
369 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
370 BlackKing, BlackBishop, BlackKnight, BlackRook }
372 Board twoKingsPosition = {
373 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
374 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
375 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
376 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
377 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
378 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
379 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
380 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
381 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
382 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
383 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
384 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
385 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
386 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
387 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
388 BlackKing, BlackKing, BlackKnight, BlackRook }
392 /* Convert str to a rating. Checks for special cases of "----",
393 "++++", etc. Also strips ()'s */
395 string_to_rating(str)
398 while(*str && !isdigit(*str)) ++str;
400 return 0; /* One of the special "no rating" cases */
408 /* Init programStats */
409 programStats.movelist[0] = 0;
410 programStats.depth = 0;
411 programStats.nr_moves = 0;
412 programStats.moves_left = 0;
413 programStats.nodes = 0;
414 programStats.time = 100;
415 programStats.score = 0;
416 programStats.got_only_move = 0;
417 programStats.got_fail = 0;
418 programStats.line_is_book = 0;
424 int matched, min, sec;
426 GetTimeMark(&programStartTime);
429 programStats.ok_to_send = 1;
430 programStats.seen_stat = 0;
433 * Initialize game list
439 * Internet chess server status
441 if (appData.icsActive) {
442 appData.matchMode = FALSE;
443 appData.matchGames = 0;
445 appData.noChessProgram = !appData.zippyPlay;
447 appData.zippyPlay = FALSE;
448 appData.zippyTalk = FALSE;
449 appData.noChessProgram = TRUE;
451 if (*appData.icsHelper != NULLCHAR) {
452 appData.useTelnet = TRUE;
453 appData.telnetProgram = appData.icsHelper;
456 appData.zippyTalk = appData.zippyPlay = FALSE;
460 * Parse timeControl resource
462 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
463 appData.movesPerSession)) {
465 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
466 DisplayFatalError(buf, 0, 2);
470 * Parse searchTime resource
472 if (*appData.searchTime != NULLCHAR) {
473 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
475 searchTime = min * 60;
476 } else if (matched == 2) {
477 searchTime = min * 60 + sec;
480 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
481 DisplayFatalError(buf, 0, 2);
485 first.which = "first";
486 second.which = "second";
487 first.maybeThinking = second.maybeThinking = FALSE;
488 first.pr = second.pr = NoProc;
489 first.isr = second.isr = NULL;
490 first.sendTime = second.sendTime = 2;
491 first.sendDrawOffers = 1;
492 if (appData.firstPlaysBlack) {
493 first.twoMachinesColor = "black\n";
494 second.twoMachinesColor = "white\n";
496 first.twoMachinesColor = "white\n";
497 second.twoMachinesColor = "black\n";
499 first.program = appData.firstChessProgram;
500 second.program = appData.secondChessProgram;
501 first.host = appData.firstHost;
502 second.host = appData.secondHost;
503 first.dir = appData.firstDirectory;
504 second.dir = appData.secondDirectory;
505 first.other = &second;
506 second.other = &first;
507 first.initString = appData.initString;
508 second.initString = appData.secondInitString;
509 first.computerString = appData.firstComputerString;
510 second.computerString = appData.secondComputerString;
511 first.useSigint = second.useSigint = TRUE;
512 first.useSigterm = second.useSigterm = TRUE;
513 first.reuse = appData.reuseFirst;
514 second.reuse = appData.reuseSecond;
515 first.useSetboard = second.useSetboard = FALSE;
516 first.useSAN = second.useSAN = FALSE;
517 first.usePing = second.usePing = FALSE;
518 first.lastPing = second.lastPing = 0;
519 first.lastPong = second.lastPong = 0;
520 first.usePlayother = second.usePlayother = FALSE;
521 first.useColors = second.useColors = TRUE;
522 first.useUsermove = second.useUsermove = FALSE;
523 first.sendICS = second.sendICS = FALSE;
524 first.sendName = second.sendName = appData.icsActive;
525 first.sdKludge = second.sdKludge = FALSE;
526 first.stKludge = second.stKludge = FALSE;
527 TidyProgramName(first.program, first.host, first.tidy);
528 TidyProgramName(second.program, second.host, second.tidy);
529 first.matchWins = second.matchWins = 0;
530 strcpy(first.variants, appData.variant);
531 strcpy(second.variants, appData.variant);
532 first.analysisSupport = second.analysisSupport = 2; /* detect */
533 first.analyzing = second.analyzing = FALSE;
534 first.initDone = second.initDone = FALSE;
536 if (appData.firstProtocolVersion > PROTOVER ||
537 appData.firstProtocolVersion < 1) {
539 sprintf(buf, _("protocol version %d not supported"),
540 appData.firstProtocolVersion);
541 DisplayFatalError(buf, 0, 2);
543 first.protocolVersion = appData.firstProtocolVersion;
546 if (appData.secondProtocolVersion > PROTOVER ||
547 appData.secondProtocolVersion < 1) {
549 sprintf(buf, _("protocol version %d not supported"),
550 appData.secondProtocolVersion);
551 DisplayFatalError(buf, 0, 2);
553 second.protocolVersion = appData.secondProtocolVersion;
556 if (appData.icsActive) {
557 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
558 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
559 appData.clockMode = FALSE;
560 first.sendTime = second.sendTime = 0;
564 /* Override some settings from environment variables, for backward
565 compatibility. Unfortunately it's not feasible to have the env
566 vars just set defaults, at least in xboard. Ugh.
568 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
573 if (appData.noChessProgram) {
574 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
575 + strlen(PATCHLEVEL));
576 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
580 while (*q != ' ' && *q != NULLCHAR) q++;
582 while (p > first.program && *(p-1) != '/') p--;
583 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
584 + strlen(PATCHLEVEL) + (q - p));
585 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
586 strncat(programVersion, p, q - p);
589 if (!appData.icsActive) {
591 /* Check for variants that are supported only in ICS mode,
592 or not at all. Some that are accepted here nevertheless
593 have bugs; see comments below.
595 VariantClass variant = StringToVariant(appData.variant);
597 case VariantBughouse: /* need four players and two boards */
598 case VariantKriegspiel: /* need to hide pieces and move details */
599 case VariantFischeRandom: /* castling doesn't work, shuffle not done */
600 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
601 DisplayFatalError(buf, 0, 2);
605 case VariantLoadable:
615 sprintf(buf, _("Unknown variant name %s"), appData.variant);
616 DisplayFatalError(buf, 0, 2);
619 case VariantNormal: /* definitely works! */
620 case VariantWildCastle: /* pieces not automatically shuffled */
621 case VariantNoCastle: /* pieces not automatically shuffled */
622 case VariantCrazyhouse: /* holdings not shown,
623 offboard interposition not understood */
624 case VariantLosers: /* should work except for win condition,
625 and doesn't know captures are mandatory */
626 case VariantSuicide: /* should work except for win condition,
627 and doesn't know captures are mandatory */
628 case VariantGiveaway: /* should work except for win condition,
629 and doesn't know captures are mandatory */
630 case VariantTwoKings: /* should work */
631 case VariantAtomic: /* should work except for win condition */
632 case Variant3Check: /* should work except for win condition */
633 case VariantShatranj: /* might work if TestLegality is off */
640 ParseTimeControl(tc, ti, mps)
645 int matched, min, sec;
647 matched = sscanf(tc, "%d:%d", &min, &sec);
649 timeControl = min * 60 * 1000;
650 } else if (matched == 2) {
651 timeControl = (min * 60 + sec) * 1000;
657 timeIncrement = ti * 1000; /* convert to ms */
661 movesPerSession = mps;
669 if (appData.debugMode) {
670 fprintf(debugFP, "%s\n", programVersion);
673 if (appData.matchGames > 0) {
674 appData.matchMode = TRUE;
675 } else if (appData.matchMode) {
676 appData.matchGames = 1;
679 if (appData.noChessProgram || first.protocolVersion == 1) {
682 /* kludge: allow timeout for initial "feature" commands */
684 DisplayMessage("", "Starting chess program");
685 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
690 InitBackEnd3 P((void))
692 GameMode initialMode;
696 InitChessProgram(&first);
698 if (appData.icsActive) {
701 if (*appData.icsCommPort != NULLCHAR) {
702 sprintf(buf, _("Could not open comm port %s"),
703 appData.icsCommPort);
705 sprintf(buf, _("Could not connect to host %s, port %s"),
706 appData.icsHost, appData.icsPort);
708 DisplayFatalError(buf, err, 1);
713 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
715 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
716 } else if (appData.noChessProgram) {
722 if (*appData.cmailGameName != NULLCHAR) {
724 OpenLoopback(&cmailPR);
726 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
730 DisplayMessage("", "");
731 if (StrCaseCmp(appData.initialMode, "") == 0) {
732 initialMode = BeginningOfGame;
733 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
734 initialMode = TwoMachinesPlay;
735 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
736 initialMode = AnalyzeFile;
737 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
738 initialMode = AnalyzeMode;
739 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
740 initialMode = MachinePlaysWhite;
741 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
742 initialMode = MachinePlaysBlack;
743 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
744 initialMode = EditGame;
745 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
746 initialMode = EditPosition;
747 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
748 initialMode = Training;
750 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
751 DisplayFatalError(buf, 0, 2);
755 if (appData.matchMode) {
756 /* Set up machine vs. machine match */
757 if (appData.noChessProgram) {
758 DisplayFatalError(_("Can't have a match with no chess programs"),
764 if (*appData.loadGameFile != NULLCHAR) {
765 if (!LoadGameFromFile(appData.loadGameFile,
766 appData.loadGameIndex,
767 appData.loadGameFile, FALSE)) {
768 DisplayFatalError(_("Bad game file"), 0, 1);
771 } else if (*appData.loadPositionFile != NULLCHAR) {
772 if (!LoadPositionFromFile(appData.loadPositionFile,
773 appData.loadPositionIndex,
774 appData.loadPositionFile)) {
775 DisplayFatalError(_("Bad position file"), 0, 1);
780 } else if (*appData.cmailGameName != NULLCHAR) {
781 /* Set up cmail mode */
782 ReloadCmailMsgEvent(TRUE);
784 /* Set up other modes */
785 if (initialMode == AnalyzeFile) {
786 if (*appData.loadGameFile == NULLCHAR) {
787 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
791 if (*appData.loadGameFile != NULLCHAR) {
792 (void) LoadGameFromFile(appData.loadGameFile,
793 appData.loadGameIndex,
794 appData.loadGameFile, TRUE);
795 } else if (*appData.loadPositionFile != NULLCHAR) {
796 (void) LoadPositionFromFile(appData.loadPositionFile,
797 appData.loadPositionIndex,
798 appData.loadPositionFile);
800 if (initialMode == AnalyzeMode) {
801 if (appData.noChessProgram) {
802 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
805 if (appData.icsActive) {
806 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
810 } else if (initialMode == AnalyzeFile) {
811 ShowThinkingEvent(TRUE);
813 AnalysisPeriodicEvent(1);
814 } else if (initialMode == MachinePlaysWhite) {
815 if (appData.noChessProgram) {
816 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
820 if (appData.icsActive) {
821 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
826 } else if (initialMode == MachinePlaysBlack) {
827 if (appData.noChessProgram) {
828 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
832 if (appData.icsActive) {
833 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
838 } else if (initialMode == TwoMachinesPlay) {
839 if (appData.noChessProgram) {
840 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
844 if (appData.icsActive) {
845 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
850 } else if (initialMode == EditGame) {
852 } else if (initialMode == EditPosition) {
854 } else if (initialMode == Training) {
855 if (*appData.loadGameFile == NULLCHAR) {
856 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
865 * Establish will establish a contact to a remote host.port.
866 * Sets icsPR to a ProcRef for a process (or pseudo-process)
867 * used to talk to the host.
868 * Returns 0 if okay, error code if not.
875 if (*appData.icsCommPort != NULLCHAR) {
876 /* Talk to the host through a serial comm port */
877 return OpenCommPort(appData.icsCommPort, &icsPR);
879 } else if (*appData.gateway != NULLCHAR) {
880 if (*appData.remoteShell == NULLCHAR) {
881 /* Use the rcmd protocol to run telnet program on a gateway host */
882 sprintf(buf, "%s %s %s",
883 appData.telnetProgram, appData.icsHost, appData.icsPort);
884 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
887 /* Use the rsh program to run telnet program on a gateway host */
888 if (*appData.remoteUser == NULLCHAR) {
889 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
890 appData.gateway, appData.telnetProgram,
891 appData.icsHost, appData.icsPort);
893 sprintf(buf, "%s %s -l %s %s %s %s",
894 appData.remoteShell, appData.gateway,
895 appData.remoteUser, appData.telnetProgram,
896 appData.icsHost, appData.icsPort);
898 return StartChildProcess(buf, "", &icsPR);
901 } else if (appData.useTelnet) {
902 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
905 /* TCP socket interface differs somewhat between
906 Unix and NT; handle details in the front end.
908 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
913 show_bytes(fp, buf, count)
919 if (*buf < 040 || *(unsigned char *) buf > 0177) {
920 fprintf(fp, "\\%03o", *buf & 0xff);
929 /* Returns an errno value */
931 OutputMaybeTelnet(pr, message, count, outError)
937 char buf[8192], *p, *q, *buflim;
938 int left, newcount, outcount;
940 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
941 *appData.gateway != NULLCHAR) {
942 if (appData.debugMode) {
943 fprintf(debugFP, ">ICS: ");
944 show_bytes(debugFP, message, count);
945 fprintf(debugFP, "\n");
947 return OutputToProcess(pr, message, count, outError);
950 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
957 if (appData.debugMode) {
958 fprintf(debugFP, ">ICS: ");
959 show_bytes(debugFP, buf, newcount);
960 fprintf(debugFP, "\n");
962 outcount = OutputToProcess(pr, buf, newcount, outError);
963 if (outcount < newcount) return -1; /* to be sure */
970 } else if (((unsigned char) *p) == TN_IAC) {
971 *q++ = (char) TN_IAC;
978 if (appData.debugMode) {
979 fprintf(debugFP, ">ICS: ");
980 show_bytes(debugFP, buf, newcount);
981 fprintf(debugFP, "\n");
983 outcount = OutputToProcess(pr, buf, newcount, outError);
984 if (outcount < newcount) return -1; /* to be sure */
989 read_from_player(isr, closure, message, count, error)
996 int outError, outCount;
997 static int gotEof = 0;
999 /* Pass data read from player on to ICS */
1002 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1003 if (outCount < count) {
1004 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1006 } else if (count < 0) {
1007 RemoveInputSource(isr);
1008 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1009 } else if (gotEof++ > 0) {
1010 RemoveInputSource(isr);
1011 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1019 int count, outCount, outError;
1021 if (icsPR == NULL) return;
1024 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1025 if (outCount < count) {
1026 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1030 /* This is used for sending logon scripts to the ICS. Sending
1031 without a delay causes problems when using timestamp on ICC
1032 (at least on my machine). */
1034 SendToICSDelayed(s,msdelay)
1038 int count, outCount, outError;
1040 if (icsPR == NULL) return;
1043 if (appData.debugMode) {
1044 fprintf(debugFP, ">ICS: ");
1045 show_bytes(debugFP, s, count);
1046 fprintf(debugFP, "\n");
1048 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1050 if (outCount < count) {
1051 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1056 /* Remove all highlighting escape sequences in s
1057 Also deletes any suffix starting with '('
1060 StripHighlightAndTitle(s)
1063 static char retbuf[MSG_SIZ];
1066 while (*s != NULLCHAR) {
1067 while (*s == '\033') {
1068 while (*s != NULLCHAR && !isalpha(*s)) s++;
1069 if (*s != NULLCHAR) s++;
1071 while (*s != NULLCHAR && *s != '\033') {
1072 if (*s == '(' || *s == '[') {
1083 /* Remove all highlighting escape sequences in s */
1088 static char retbuf[MSG_SIZ];
1091 while (*s != NULLCHAR) {
1092 while (*s == '\033') {
1093 while (*s != NULLCHAR && !isalpha(*s)) s++;
1094 if (*s != NULLCHAR) s++;
1096 while (*s != NULLCHAR && *s != '\033') {
1104 char *variantNames[] = VARIANT_NAMES;
1109 return variantNames[v];
1113 /* Identify a variant from the strings the chess servers use or the
1114 PGN Variant tag names we use. */
1121 VariantClass v = VariantNormal;
1122 int i, found = FALSE;
1127 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1128 if (StrCaseStr(e, variantNames[i])) {
1129 v = (VariantClass) i;
1136 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1137 || StrCaseStr(e, "wild/fr")) {
1138 v = VariantFischeRandom;
1139 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1140 (i = 1, p = StrCaseStr(e, "w"))) {
1142 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1149 case 0: /* FICS only, actually */
1151 /* Castling legal even if K starts on d-file */
1152 v = VariantWildCastle;
1157 /* Castling illegal even if K & R happen to start in
1158 normal positions. */
1159 v = VariantNoCastle;
1172 /* Castling legal iff K & R start in normal positions */
1178 /* Special wilds for position setup; unclear what to do here */
1179 v = VariantLoadable;
1182 /* Bizarre ICC game */
1183 v = VariantTwoKings;
1186 v = VariantKriegspiel;
1192 v = VariantFischeRandom;
1195 v = VariantCrazyhouse;
1198 v = VariantBughouse;
1204 /* Not quite the same as FICS suicide! */
1205 v = VariantGiveaway;
1211 v = VariantShatranj;
1214 /* Temporary names for future ICC types. The name *will* change in
1215 the next xboard/WinBoard release after ICC defines it. */
1242 /* Found "wild" or "w" in the string but no number;
1243 must assume it's normal chess. */
1247 sprintf(buf, "Unknown wild type %d", wnum);
1248 DisplayError(buf, 0);
1254 if (appData.debugMode) {
1255 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1256 e, wnum, VariantName(v));
1261 static int leftover_start = 0, leftover_len = 0;
1262 char star_match[STAR_MATCH_N][MSG_SIZ];
1264 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1265 advance *index beyond it, and set leftover_start to the new value of
1266 *index; else return FALSE. If pattern contains the character '*', it
1267 matches any sequence of characters not containing '\r', '\n', or the
1268 character following the '*' (if any), and the matched sequence(s) are
1269 copied into star_match.
1272 looking_at(buf, index, pattern)
1277 char *bufp = &buf[*index], *patternp = pattern;
1279 char *matchp = star_match[0];
1282 if (*patternp == NULLCHAR) {
1283 *index = leftover_start = bufp - buf;
1287 if (*bufp == NULLCHAR) return FALSE;
1288 if (*patternp == '*') {
1289 if (*bufp == *(patternp + 1)) {
1291 matchp = star_match[++star_count];
1295 } else if (*bufp == '\n' || *bufp == '\r') {
1297 if (*patternp == NULLCHAR)
1302 *matchp++ = *bufp++;
1306 if (*patternp != *bufp) return FALSE;
1313 SendToPlayer(data, length)
1317 int error, outCount;
1318 outCount = OutputToProcess(NoProc, data, length, &error);
1319 if (outCount < length) {
1320 DisplayFatalError(_("Error writing to display"), error, 1);
1325 PackHolding(packed, holding)
1337 switch (runlength) {
1348 sprintf(q, "%d", runlength);
1360 /* Telnet protocol requests from the front end */
1362 TelnetRequest(ddww, option)
1363 unsigned char ddww, option;
1365 unsigned char msg[3];
1366 int outCount, outError;
1368 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1370 if (appData.debugMode) {
1371 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1387 sprintf(buf1, "%d", ddww);
1396 sprintf(buf2, "%d", option);
1399 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1404 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1413 if (!appData.icsActive) return;
1414 TelnetRequest(TN_DO, TN_ECHO);
1420 if (!appData.icsActive) return;
1421 TelnetRequest(TN_DONT, TN_ECHO);
1424 static int loggedOn = FALSE;
1426 /*-- Game start info cache: --*/
1428 char gs_kind[MSG_SIZ];
1429 static char player1Name[128] = "";
1430 static char player2Name[128] = "";
1431 static int player1Rating = -1;
1432 static int player2Rating = -1;
1433 /*----------------------------*/
1436 read_from_ics(isr, closure, data, count, error)
1443 #define BUF_SIZE 8192
1444 #define STARTED_NONE 0
1445 #define STARTED_MOVES 1
1446 #define STARTED_BOARD 2
1447 #define STARTED_OBSERVE 3
1448 #define STARTED_HOLDINGS 4
1449 #define STARTED_CHATTER 5
1450 #define STARTED_COMMENT 6
1451 #define STARTED_MOVES_NOHIDE 7
1453 static int started = STARTED_NONE;
1454 static char parse[20000];
1455 static int parse_pos = 0;
1456 static char buf[BUF_SIZE + 1];
1457 static int firstTime = TRUE, intfSet = FALSE;
1458 static ColorClass curColor = ColorNormal;
1459 static ColorClass prevColor = ColorNormal;
1460 static int savingComment = FALSE;
1467 /* For zippy color lines of winboard
\r
1468 * cleanup for gcc compiler */
\r
1474 if (appData.debugMode) {
1476 fprintf(debugFP, "<ICS: ");
1477 show_bytes(debugFP, data, count);
1478 fprintf(debugFP, "\n");
1484 /* If last read ended with a partial line that we couldn't parse,
1485 prepend it to the new read and try again. */
1486 if (leftover_len > 0) {
1487 for (i=0; i<leftover_len; i++)
1488 buf[i] = buf[leftover_start + i];
1491 /* Copy in new characters, removing nulls and \r's */
1492 buf_len = leftover_len;
1493 for (i = 0; i < count; i++) {
1494 if (data[i] != NULLCHAR && data[i] != '\r')
1495 buf[buf_len++] = data[i];
1498 buf[buf_len] = NULLCHAR;
1499 next_out = leftover_len;
1503 while (i < buf_len) {
1504 /* Deal with part of the TELNET option negotiation
1505 protocol. We refuse to do anything beyond the
1506 defaults, except that we allow the WILL ECHO option,
1507 which ICS uses to turn off password echoing when we are
1508 directly connected to it. We reject this option
1509 if localLineEditing mode is on (always on in xboard)
1510 and we are talking to port 23, which might be a real
1511 telnet server that will try to keep WILL ECHO on permanently.
1513 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1514 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1515 unsigned char option;
1517 switch ((unsigned char) buf[++i]) {
1519 if (appData.debugMode)
1520 fprintf(debugFP, "\n<WILL ");
1521 switch (option = (unsigned char) buf[++i]) {
1523 if (appData.debugMode)
1524 fprintf(debugFP, "ECHO ");
1525 /* Reply only if this is a change, according
1526 to the protocol rules. */
1527 if (remoteEchoOption) break;
1528 if (appData.localLineEditing &&
1529 atoi(appData.icsPort) == TN_PORT) {
1530 TelnetRequest(TN_DONT, TN_ECHO);
1533 TelnetRequest(TN_DO, TN_ECHO);
1534 remoteEchoOption = TRUE;
1538 if (appData.debugMode)
1539 fprintf(debugFP, "%d ", option);
1540 /* Whatever this is, we don't want it. */
1541 TelnetRequest(TN_DONT, option);
1546 if (appData.debugMode)
1547 fprintf(debugFP, "\n<WONT ");
1548 switch (option = (unsigned char) buf[++i]) {
1550 if (appData.debugMode)
1551 fprintf(debugFP, "ECHO ");
1552 /* Reply only if this is a change, according
1553 to the protocol rules. */
1554 if (!remoteEchoOption) break;
1556 TelnetRequest(TN_DONT, TN_ECHO);
1557 remoteEchoOption = FALSE;
1560 if (appData.debugMode)
1561 fprintf(debugFP, "%d ", (unsigned char) option);
1562 /* Whatever this is, it must already be turned
1563 off, because we never agree to turn on
1564 anything non-default, so according to the
1565 protocol rules, we don't reply. */
1570 if (appData.debugMode)
1571 fprintf(debugFP, "\n<DO ");
1572 switch (option = (unsigned char) buf[++i]) {
1574 /* Whatever this is, we refuse to do it. */
1575 if (appData.debugMode)
1576 fprintf(debugFP, "%d ", option);
1577 TelnetRequest(TN_WONT, option);
1582 if (appData.debugMode)
1583 fprintf(debugFP, "\n<DONT ");
1584 switch (option = (unsigned char) buf[++i]) {
1586 if (appData.debugMode)
1587 fprintf(debugFP, "%d ", option);
1588 /* Whatever this is, we are already not doing
1589 it, because we never agree to do anything
1590 non-default, so according to the protocol
1591 rules, we don't reply. */
1596 if (appData.debugMode)
1597 fprintf(debugFP, "\n<IAC ");
1598 /* Doubled IAC; pass it through */
1602 if (appData.debugMode)
1603 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1604 /* Drop all other telnet commands on the floor */
1607 if (oldi > next_out)
1608 SendToPlayer(&buf[next_out], oldi - next_out);
1614 /* OK, this at least will *usually* work */
1615 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1619 if (loggedOn && !intfSet) {
1620 if (ics_type == ICS_ICC) {
1622 "/set-quietly interface %s\n/set-quietly style 12\n",
1625 } else if (ics_type == ICS_CHESSNET) {
1626 sprintf(str, "/style 12\n");
1628 strcpy(str, "alias $ @\n$set interface ");
1629 strcat(str, programVersion);
1630 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1632 strcat(str, "$iset nohighlight 1\n");
1634 strcat(str, "$iset lock 1\n$style 12\n");
1640 if (started == STARTED_COMMENT) {
1641 /* Accumulate characters in comment */
1642 parse[parse_pos++] = buf[i];
1643 if (buf[i] == '\n') {
1644 parse[parse_pos] = NULLCHAR;
1645 AppendComment(forwardMostMove, StripHighlight(parse));
1646 started = STARTED_NONE;
1648 /* Don't match patterns against characters in chatter */
1653 if (started == STARTED_CHATTER) {
1654 if (buf[i] != '\n') {
1655 /* Don't match patterns against characters in chatter */
1659 started = STARTED_NONE;
1662 /* Kludge to deal with rcmd protocol */
1663 if (firstTime && looking_at(buf, &i, "\001*")) {
1664 DisplayFatalError(&buf[1], 0, 1);
1670 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1673 if (appData.debugMode)
1674 fprintf(debugFP, "ics_type %d\n", ics_type);
1677 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1678 ics_type = ICS_FICS;
1680 if (appData.debugMode)
1681 fprintf(debugFP, "ics_type %d\n", ics_type);
1684 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1685 ics_type = ICS_CHESSNET;
1687 if (appData.debugMode)
1688 fprintf(debugFP, "ics_type %d\n", ics_type);
1693 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1694 looking_at(buf, &i, "Logging you in as \"*\"") ||
1695 looking_at(buf, &i, "will be \"*\""))) {
1696 strcpy(ics_handle, star_match[0]);
1700 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1702 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1703 DisplayIcsInteractionTitle(buf);
1704 have_set_title = TRUE;
1707 /* skip finger notes */
1708 if (started == STARTED_NONE &&
1709 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1710 (buf[i] == '1' && buf[i+1] == '0')) &&
1711 buf[i+2] == ':' && buf[i+3] == ' ') {
1712 started = STARTED_CHATTER;
1717 /* skip formula vars */
1718 if (started == STARTED_NONE &&
1719 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1720 started = STARTED_CHATTER;
1726 if (appData.zippyTalk || appData.zippyPlay) {
1729 /* Backup adress for color zippy lines */
\r
1731 if (first.initDone && loggedOn == TRUE)
\r
1732 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
1733 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
1735 if (ZippyControl(buf, &i) ||
1736 ZippyConverse(buf, &i) ||
1737 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1744 if (/* Don't color "message" or "messages" output */
1745 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1746 looking_at(buf, &i, "*. * at *:*: ") ||
1747 looking_at(buf, &i, "--* (*:*): ") ||
1748 /* Regular tells and says */
1749 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1750 looking_at(buf, &i, "* (your partner) tells you: ") ||
1751 looking_at(buf, &i, "* says: ") ||
1752 /* Message notifications (same color as tells) */
1753 looking_at(buf, &i, "* has left a message ") ||
1754 looking_at(buf, &i, "* just sent you a message:\n") ||
1755 /* Whispers and kibitzes */
1756 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1757 looking_at(buf, &i, "* kibitzes: ") ||
1759 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1761 if (tkind == 1 && strchr(star_match[0], ':')) {
1762 /* Avoid "tells you:" spoofs in channels */
1765 if (star_match[0][0] == NULLCHAR ||
1766 strchr(star_match[0], ' ') ||
1767 (tkind == 3 && strchr(star_match[1], ' '))) {
1768 /* Reject bogus matches */
1771 if (appData.colorize) {
1772 if (oldi > next_out) {
1773 SendToPlayer(&buf[next_out], oldi - next_out);
1778 Colorize(ColorTell, FALSE);
1779 curColor = ColorTell;
1782 Colorize(ColorKibitz, FALSE);
1783 curColor = ColorKibitz;
1786 p = strrchr(star_match[1], '(');
1793 Colorize(ColorChannel1, FALSE);
1794 curColor = ColorChannel1;
1796 Colorize(ColorChannel, FALSE);
1797 curColor = ColorChannel;
1801 curColor = ColorNormal;
1805 if (started == STARTED_NONE && appData.autoComment &&
1806 (gameMode == IcsObserving ||
1807 gameMode == IcsPlayingWhite ||
1808 gameMode == IcsPlayingBlack)) {
1809 parse_pos = i - oldi;
1810 memcpy(parse, &buf[oldi], parse_pos);
1811 parse[parse_pos] = NULLCHAR;
1812 started = STARTED_COMMENT;
1813 savingComment = TRUE;
1815 started = STARTED_CHATTER;
1816 savingComment = FALSE;
1823 if (looking_at(buf, &i, "* s-shouts: ") ||
1824 looking_at(buf, &i, "* c-shouts: ")) {
1825 if (appData.colorize) {
1826 if (oldi > next_out) {
1827 SendToPlayer(&buf[next_out], oldi - next_out);
1830 Colorize(ColorSShout, FALSE);
1831 curColor = ColorSShout;
1834 started = STARTED_CHATTER;
1838 if (looking_at(buf, &i, "--->")) {
1843 if (looking_at(buf, &i, "* shouts: ") ||
1844 looking_at(buf, &i, "--> ")) {
1845 if (appData.colorize) {
1846 if (oldi > next_out) {
1847 SendToPlayer(&buf[next_out], oldi - next_out);
1850 Colorize(ColorShout, FALSE);
1851 curColor = ColorShout;
1854 started = STARTED_CHATTER;
1858 if (looking_at( buf, &i, "Challenge:")) {
1859 if (appData.colorize) {
1860 if (oldi > next_out) {
1861 SendToPlayer(&buf[next_out], oldi - next_out);
1864 Colorize(ColorChallenge, FALSE);
1865 curColor = ColorChallenge;
1871 if (looking_at(buf, &i, "* offers you") ||
1872 looking_at(buf, &i, "* offers to be") ||
1873 looking_at(buf, &i, "* would like to") ||
1874 looking_at(buf, &i, "* requests to") ||
1875 looking_at(buf, &i, "Your opponent offers") ||
1876 looking_at(buf, &i, "Your opponent requests")) {
1878 if (appData.colorize) {
1879 if (oldi > next_out) {
1880 SendToPlayer(&buf[next_out], oldi - next_out);
1883 Colorize(ColorRequest, FALSE);
1884 curColor = ColorRequest;
1889 if (looking_at(buf, &i, "* (*) seeking")) {
1890 if (appData.colorize) {
1891 if (oldi > next_out) {
1892 SendToPlayer(&buf[next_out], oldi - next_out);
1895 Colorize(ColorSeek, FALSE);
1896 curColor = ColorSeek;
1901 if (looking_at(buf, &i, "\\ ")) {
1902 if (prevColor != ColorNormal) {
1903 if (oldi > next_out) {
1904 SendToPlayer(&buf[next_out], oldi - next_out);
1907 Colorize(prevColor, TRUE);
1908 curColor = prevColor;
1910 if (savingComment) {
1911 parse_pos = i - oldi;
1912 memcpy(parse, &buf[oldi], parse_pos);
1913 parse[parse_pos] = NULLCHAR;
1914 started = STARTED_COMMENT;
1916 started = STARTED_CHATTER;
1921 if (looking_at(buf, &i, "Black Strength :") ||
1922 looking_at(buf, &i, "<<< style 10 board >>>") ||
1923 looking_at(buf, &i, "<10>") ||
1924 looking_at(buf, &i, "#@#")) {
1925 /* Wrong board style */
1927 SendToICS(ics_prefix);
1928 SendToICS("set style 12\n");
1929 SendToICS(ics_prefix);
1930 SendToICS("refresh\n");
1934 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1936 have_sent_ICS_logon = 1;
1940 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1941 (looking_at(buf, &i, "\n<12> ") ||
1942 looking_at(buf, &i, "<12> "))) {
1944 if (oldi > next_out) {
1945 SendToPlayer(&buf[next_out], oldi - next_out);
1948 started = STARTED_BOARD;
1953 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1954 looking_at(buf, &i, "<b1> ")) {
1955 if (oldi > next_out) {
1956 SendToPlayer(&buf[next_out], oldi - next_out);
1959 started = STARTED_HOLDINGS;
1964 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1966 /* Header for a move list -- first line */
1968 switch (ics_getting_history) {
1972 case BeginningOfGame:
1973 /* User typed "moves" or "oldmoves" while we
1974 were idle. Pretend we asked for these
1975 moves and soak them up so user can step
1976 through them and/or save them.
1979 gameMode = IcsObserving;
1982 ics_getting_history = H_GOT_UNREQ_HEADER;
1984 case EditGame: /*?*/
1985 case EditPosition: /*?*/
1986 /* Should above feature work in these modes too? */
1987 /* For now it doesn't */
1988 ics_getting_history = H_GOT_UNWANTED_HEADER;
1991 ics_getting_history = H_GOT_UNWANTED_HEADER;
1996 /* Is this the right one? */
1997 if (gameInfo.white && gameInfo.black &&
1998 strcmp(gameInfo.white, star_match[0]) == 0 &&
1999 strcmp(gameInfo.black, star_match[2]) == 0) {
2001 ics_getting_history = H_GOT_REQ_HEADER;
2004 case H_GOT_REQ_HEADER:
2005 case H_GOT_UNREQ_HEADER:
2006 case H_GOT_UNWANTED_HEADER:
2007 case H_GETTING_MOVES:
2008 /* Should not happen */
2009 DisplayError(_("Error gathering move list: two headers"), 0);
2010 ics_getting_history = H_FALSE;
2014 /* Save player ratings into gameInfo if needed */
2015 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2016 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2017 (gameInfo.whiteRating == -1 ||
2018 gameInfo.blackRating == -1)) {
2020 gameInfo.whiteRating = string_to_rating(star_match[1]);
2021 gameInfo.blackRating = string_to_rating(star_match[3]);
2022 if (appData.debugMode)
2023 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2024 gameInfo.whiteRating, gameInfo.blackRating);
2029 if (looking_at(buf, &i,
2030 "* * match, initial time: * minute*, increment: * second")) {
2031 /* Header for a move list -- second line */
2032 /* Initial board will follow if this is a wild game */
2034 if (gameInfo.event != NULL) free(gameInfo.event);
2035 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2036 gameInfo.event = StrSave(str);
2037 gameInfo.variant = StringToVariant(gameInfo.event);
2041 if (looking_at(buf, &i, "Move ")) {
2042 /* Beginning of a move list */
2043 switch (ics_getting_history) {
2045 /* Normally should not happen */
2046 /* Maybe user hit reset while we were parsing */
2049 /* Happens if we are ignoring a move list that is not
2050 * the one we just requested. Common if the user
2051 * tries to observe two games without turning off
2054 case H_GETTING_MOVES:
2055 /* Should not happen */
2056 DisplayError(_("Error gathering move list: nested"), 0);
2057 ics_getting_history = H_FALSE;
2059 case H_GOT_REQ_HEADER:
2060 ics_getting_history = H_GETTING_MOVES;
2061 started = STARTED_MOVES;
2063 if (oldi > next_out) {
2064 SendToPlayer(&buf[next_out], oldi - next_out);
2067 case H_GOT_UNREQ_HEADER:
2068 ics_getting_history = H_GETTING_MOVES;
2069 started = STARTED_MOVES_NOHIDE;
2072 case H_GOT_UNWANTED_HEADER:
2073 ics_getting_history = H_FALSE;
2079 if (looking_at(buf, &i, "% ") ||
2080 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2081 && looking_at(buf, &i, "}*"))) {
2082 savingComment = FALSE;
2085 case STARTED_MOVES_NOHIDE:
2086 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2087 parse[parse_pos + i - oldi] = NULLCHAR;
2088 ParseGameHistory(parse);
2090 if (appData.zippyPlay && first.initDone) {
2091 FeedMovesToProgram(&first, forwardMostMove);
2092 if (gameMode == IcsPlayingWhite) {
2093 if (WhiteOnMove(forwardMostMove)) {
2094 if (first.sendTime) {
2095 if (first.useColors) {
2096 SendToProgram("black\n", &first);
2098 SendTimeRemaining(&first, TRUE);
2100 if (first.useColors) {
2101 SendToProgram("white\ngo\n", &first);
2103 SendToProgram("go\n", &first);
2105 first.maybeThinking = TRUE;
2107 if (first.usePlayother) {
2108 if (first.sendTime) {
2109 SendTimeRemaining(&first, TRUE);
2111 SendToProgram("playother\n", &first);
2117 } else if (gameMode == IcsPlayingBlack) {
2118 if (!WhiteOnMove(forwardMostMove)) {
2119 if (first.sendTime) {
2120 if (first.useColors) {
2121 SendToProgram("white\n", &first);
2123 SendTimeRemaining(&first, FALSE);
2125 if (first.useColors) {
2126 SendToProgram("black\ngo\n", &first);
2128 SendToProgram("go\n", &first);
2130 first.maybeThinking = TRUE;
2132 if (first.usePlayother) {
2133 if (first.sendTime) {
2134 SendTimeRemaining(&first, FALSE);
2136 SendToProgram("playother\n", &first);
2145 if (gameMode == IcsObserving && ics_gamenum == -1) {
2146 /* Moves came from oldmoves or moves command
2147 while we weren't doing anything else.
2149 currentMove = forwardMostMove;
2150 ClearHighlights();/*!!could figure this out*/
2151 flipView = appData.flipView;
2152 DrawPosition(FALSE, boards[currentMove]);
2153 DisplayBothClocks();
2154 sprintf(str, "%s vs. %s",
2155 gameInfo.white, gameInfo.black);
2159 /* Moves were history of an active game */
2160 if (gameInfo.resultDetails != NULL) {
2161 free(gameInfo.resultDetails);
2162 gameInfo.resultDetails = NULL;
2165 HistorySet(parseList, backwardMostMove,
2166 forwardMostMove, currentMove-1);
2167 DisplayMove(currentMove - 1);
2168 if (started == STARTED_MOVES) next_out = i;
2169 started = STARTED_NONE;
2170 ics_getting_history = H_FALSE;
2173 case STARTED_OBSERVE:
2174 started = STARTED_NONE;
2175 SendToICS(ics_prefix);
2176 SendToICS("refresh\n");
2185 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2186 started == STARTED_HOLDINGS ||
2187 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2188 /* Accumulate characters in move list or board */
2189 parse[parse_pos++] = buf[i];
2192 /* Start of game messages. Mostly we detect start of game
2193 when the first board image arrives. On some versions
2194 of the ICS, though, we need to do a "refresh" after starting
2195 to observe in order to get the current board right away. */
2196 if (looking_at(buf, &i, "Adding game * to observation list")) {
2197 started = STARTED_OBSERVE;
2201 /* Handle auto-observe */
2202 if (appData.autoObserve &&
2203 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2204 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2206 /* Choose the player that was highlighted, if any. */
2207 if (star_match[0][0] == '\033' ||
2208 star_match[1][0] != '\033') {
2209 player = star_match[0];
2211 player = star_match[2];
2213 sprintf(str, "%sobserve %s\n",
2214 ics_prefix, StripHighlightAndTitle(player));
2217 /* Save ratings from notify string */
2218 strcpy(player1Name, star_match[0]);
2219 player1Rating = string_to_rating(star_match[1]);
2220 strcpy(player2Name, star_match[2]);
2221 player2Rating = string_to_rating(star_match[3]);
2223 if (appData.debugMode)
2225 "Ratings from 'Game notification:' %s %d, %s %d\n",
2226 player1Name, player1Rating,
2227 player2Name, player2Rating);
2232 /* Deal with automatic examine mode after a game,
2233 and with IcsObserving -> IcsExamining transition */
2234 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2235 looking_at(buf, &i, "has made you an examiner of game *")) {
2237 int gamenum = atoi(star_match[0]);
2238 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2239 gamenum == ics_gamenum) {
2240 /* We were already playing or observing this game;
2241 no need to refetch history */
2242 gameMode = IcsExamining;
2244 pauseExamForwardMostMove = forwardMostMove;
2245 } else if (currentMove < forwardMostMove) {
2246 ForwardInner(forwardMostMove);
2249 /* I don't think this case really can happen */
2250 SendToICS(ics_prefix);
2251 SendToICS("refresh\n");
2256 /* Error messages */
2257 if (ics_user_moved) {
2258 if (looking_at(buf, &i, "Illegal move") ||
2259 looking_at(buf, &i, "Not a legal move") ||
2260 looking_at(buf, &i, "Your king is in check") ||
2261 looking_at(buf, &i, "It isn't your turn") ||
2262 looking_at(buf, &i, "It is not your move")) {
2265 if (forwardMostMove > backwardMostMove) {
2266 currentMove = --forwardMostMove;
2267 DisplayMove(currentMove - 1); /* before DMError */
2268 DisplayMoveError("Illegal move (rejected by ICS)");
2269 DrawPosition(FALSE, boards[currentMove]);
2271 DisplayBothClocks();
2277 if (looking_at(buf, &i, "still have time") ||
2278 looking_at(buf, &i, "not out of time") ||
2279 looking_at(buf, &i, "either player is out of time") ||
2280 looking_at(buf, &i, "has timeseal; checking")) {
2281 /* We must have called his flag a little too soon */
2282 whiteFlag = blackFlag = FALSE;
2286 if (looking_at(buf, &i, "added * seconds to") ||
2287 looking_at(buf, &i, "seconds were added to")) {
2288 /* Update the clocks */
2289 SendToICS(ics_prefix);
2290 SendToICS("refresh\n");
2294 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2295 ics_clock_paused = TRUE;
2300 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2301 ics_clock_paused = FALSE;
2306 /* Grab player ratings from the Creating: message.
2307 Note we have to check for the special case when
2308 the ICS inserts things like [white] or [black]. */
2309 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2310 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2312 0 player 1 name (not necessarily white)
2314 2 empty, white, or black (IGNORED)
2315 3 player 2 name (not necessarily black)
2318 The names/ratings are sorted out when the game
2319 actually starts (below).
2321 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2322 player1Rating = string_to_rating(star_match[1]);
2323 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2324 player2Rating = string_to_rating(star_match[4]);
2326 if (appData.debugMode)
2328 "Ratings from 'Creating:' %s %d, %s %d\n",
2329 player1Name, player1Rating,
2330 player2Name, player2Rating);
2335 /* Improved generic start/end-of-game messages */
2336 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2337 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2338 /* If tkind == 0: */
2339 /* star_match[0] is the game number */
2340 /* [1] is the white player's name */
2341 /* [2] is the black player's name */
2342 /* For end-of-game: */
2343 /* [3] is the reason for the game end */
2344 /* [4] is a PGN end game-token, preceded by " " */
2345 /* For start-of-game: */
2346 /* [3] begins with "Creating" or "Continuing" */
2347 /* [4] is " *" or empty (don't care). */
2348 int gamenum = atoi(star_match[0]);
2349 char *whitename, *blackname, *why, *endtoken;
2350 ChessMove endtype = (ChessMove) 0;
2353 whitename = star_match[1];
2354 blackname = star_match[2];
2355 why = star_match[3];
2356 endtoken = star_match[4];
2358 whitename = star_match[1];
2359 blackname = star_match[3];
2360 why = star_match[5];
2361 endtoken = star_match[6];
2364 /* Game start messages */
2365 if (strncmp(why, "Creating ", 9) == 0 ||
2366 strncmp(why, "Continuing ", 11) == 0) {
2367 gs_gamenum = gamenum;
2368 strcpy(gs_kind, strchr(why, ' ') + 1);
2370 if (appData.zippyPlay) {
2371 ZippyGameStart(whitename, blackname);
2377 /* Game end messages */
2378 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2379 ics_gamenum != gamenum) {
2382 while (endtoken[0] == ' ') endtoken++;
2383 switch (endtoken[0]) {
2386 endtype = GameUnfinished;
2389 endtype = BlackWins;
2392 if (endtoken[1] == '/')
2393 endtype = GameIsDrawn;
2395 endtype = WhiteWins;
2398 GameEnds(endtype, why, GE_ICS);
2400 if (appData.zippyPlay && first.initDone) {
2401 ZippyGameEnd(endtype, why);
2402 if (first.pr == NULL) {
2403 /* Start the next process early so that we'll
2404 be ready for the next challenge */
2405 StartChessProgram(&first);
2407 /* Send "new" early, in case this command takes
2408 a long time to finish, so that we'll be ready
2409 for the next challenge. */
2416 if (looking_at(buf, &i, "Removing game * from observation") ||
2417 looking_at(buf, &i, "no longer observing game *") ||
2418 looking_at(buf, &i, "Game * (*) has no examiners")) {
2419 if (gameMode == IcsObserving &&
2420 atoi(star_match[0]) == ics_gamenum)
2425 ics_user_moved = FALSE;
2430 if (looking_at(buf, &i, "no longer examining game *")) {
2431 if (gameMode == IcsExamining &&
2432 atoi(star_match[0]) == ics_gamenum)
2436 ics_user_moved = FALSE;
2441 /* Advance leftover_start past any newlines we find,
2442 so only partial lines can get reparsed */
2443 if (looking_at(buf, &i, "\n")) {
2444 prevColor = curColor;
2445 if (curColor != ColorNormal) {
2446 if (oldi > next_out) {
2447 SendToPlayer(&buf[next_out], oldi - next_out);
2450 Colorize(ColorNormal, FALSE);
2451 curColor = ColorNormal;
2453 if (started == STARTED_BOARD) {
2454 started = STARTED_NONE;
2455 parse[parse_pos] = NULLCHAR;
2456 ParseBoard12(parse);
2459 /* Send premove here */
2460 if (appData.premove) {
2462 if (currentMove == 0 &&
2463 gameMode == IcsPlayingWhite &&
2464 appData.premoveWhite) {
2465 sprintf(str, "%s%s\n", ics_prefix,
2466 appData.premoveWhiteText);
2467 if (appData.debugMode)
2468 fprintf(debugFP, "Sending premove:\n");
2470 } else if (currentMove == 1 &&
2471 gameMode == IcsPlayingBlack &&
2472 appData.premoveBlack) {
2473 sprintf(str, "%s%s\n", ics_prefix,
2474 appData.premoveBlackText);
2475 if (appData.debugMode)
2476 fprintf(debugFP, "Sending premove:\n");
2478 } else if (gotPremove) {
2480 ClearPremoveHighlights();
2481 if (appData.debugMode)
2482 fprintf(debugFP, "Sending premove:\n");
2483 UserMoveEvent(premoveFromX, premoveFromY,
2484 premoveToX, premoveToY,
2489 /* Usually suppress following prompt */
2490 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2491 if (looking_at(buf, &i, "*% ")) {
2492 savingComment = FALSE;
2496 } else if (started == STARTED_HOLDINGS) {
2498 char new_piece[MSG_SIZ];
2499 started = STARTED_NONE;
2500 parse[parse_pos] = NULLCHAR;
2501 if (appData.debugMode)
2502 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2503 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2504 gamenum == ics_gamenum) {
2505 if (gameInfo.variant == VariantNormal) {
2506 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2507 /* Get a move list just to see the header, which
2508 will tell us whether this is really bug or zh */
2509 if (ics_getting_history == H_FALSE) {
2510 ics_getting_history = H_REQUESTED;
2511 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2515 new_piece[0] = NULLCHAR;
2516 sscanf(parse, "game %d white [%s black [%s <- %s",
2517 &gamenum, white_holding, black_holding,
2519 white_holding[strlen(white_holding)-1] = NULLCHAR;
2520 black_holding[strlen(black_holding)-1] = NULLCHAR;
2522 if (appData.zippyPlay && first.initDone) {
2523 ZippyHoldings(white_holding, black_holding,
2527 if (tinyLayout || smallLayout) {
2528 char wh[16], bh[16];
2529 PackHolding(wh, white_holding);
2530 PackHolding(bh, black_holding);
2531 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2532 gameInfo.white, gameInfo.black);
2534 sprintf(str, "%s [%s] vs. %s [%s]",
2535 gameInfo.white, white_holding,
2536 gameInfo.black, black_holding);
2538 DrawPosition(FALSE, NULL);
2541 /* Suppress following prompt */
2542 if (looking_at(buf, &i, "*% ")) {
2543 savingComment = FALSE;
2550 i++; /* skip unparsed character and loop back */
2553 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2554 started != STARTED_HOLDINGS && i > next_out) {
2555 SendToPlayer(&buf[next_out], i - next_out);
2559 leftover_len = buf_len - leftover_start;
2560 /* if buffer ends with something we couldn't parse,
2561 reparse it after appending the next read */
2563 } else if (count == 0) {
2564 RemoveInputSource(isr);
2565 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2567 DisplayFatalError(_("Error reading from ICS"), error, 1);
2572 /* Board style 12 looks like this:
2574 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
2576 * The "<12> " is stripped before it gets to this routine. The two
2577 * trailing 0's (flip state and clock ticking) are later addition, and
2578 * some chess servers may not have them, or may have only the first.
2579 * Additional trailing fields may be added in the future.
2582 #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
2584 #define RELATION_OBSERVING_PLAYED 0
2585 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2586 #define RELATION_PLAYING_MYMOVE 1
2587 #define RELATION_PLAYING_NOTMYMOVE -1
2588 #define RELATION_EXAMINING 2
2589 #define RELATION_ISOLATED_BOARD -3
2590 #define RELATION_STARTING_POSITION -4 /* FICS only */
2593 ParseBoard12(string)
2596 GameMode newGameMode;
2597 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2598 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2599 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2600 char to_play, board_chars[72];
2601 char move_str[500], str[500], elapsed_time[500];
2602 char black[32], white[32];
2604 int prevMove = currentMove;
2607 int fromX, fromY, toX, toY;
2610 fromX = fromY = toX = toY = -1;
2614 if (appData.debugMode)
2615 fprintf(debugFP, _("Parsing board: %s\n"), string);
2617 move_str[0] = NULLCHAR;
2618 elapsed_time[0] = NULLCHAR;
2619 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2620 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2621 &gamenum, white, black, &relation, &basetime, &increment,
2622 &white_stren, &black_stren, &white_time, &black_time,
2623 &moveNum, str, elapsed_time, move_str, &ics_flip,
2627 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2628 DisplayError(str, 0);
2632 /* Convert the move number to internal form */
2633 moveNum = (moveNum - 1) * 2;
2634 if (to_play == 'B') moveNum++;
2635 if (moveNum >= MAX_MOVES) {
2636 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2642 case RELATION_OBSERVING_PLAYED:
2643 case RELATION_OBSERVING_STATIC:
2644 if (gamenum == -1) {
2645 /* Old ICC buglet */
2646 relation = RELATION_OBSERVING_STATIC;
2648 newGameMode = IcsObserving;
2650 case RELATION_PLAYING_MYMOVE:
2651 case RELATION_PLAYING_NOTMYMOVE:
2653 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2654 IcsPlayingWhite : IcsPlayingBlack;
2656 case RELATION_EXAMINING:
2657 newGameMode = IcsExamining;
2659 case RELATION_ISOLATED_BOARD:
2661 /* Just display this board. If user was doing something else,
2662 we will forget about it until the next board comes. */
2663 newGameMode = IcsIdle;
2665 case RELATION_STARTING_POSITION:
2666 newGameMode = gameMode;
2670 /* Modify behavior for initial board display on move listing
2673 switch (ics_getting_history) {
2677 case H_GOT_REQ_HEADER:
2678 case H_GOT_UNREQ_HEADER:
2679 /* This is the initial position of the current game */
2680 gamenum = ics_gamenum;
2681 moveNum = 0; /* old ICS bug workaround */
2682 if (to_play == 'B') {
2683 startedFromSetupPosition = TRUE;
2684 blackPlaysFirst = TRUE;
2686 if (forwardMostMove == 0) forwardMostMove = 1;
2687 if (backwardMostMove == 0) backwardMostMove = 1;
2688 if (currentMove == 0) currentMove = 1;
2690 newGameMode = gameMode;
2691 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2693 case H_GOT_UNWANTED_HEADER:
2694 /* This is an initial board that we don't want */
2696 case H_GETTING_MOVES:
2697 /* Should not happen */
2698 DisplayError(_("Error gathering move list: extra board"), 0);
2699 ics_getting_history = H_FALSE;
2703 /* Take action if this is the first board of a new game, or of a
2704 different game than is currently being displayed. */
2705 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2706 relation == RELATION_ISOLATED_BOARD) {
2708 /* Forget the old game and get the history (if any) of the new one */
2709 if (gameMode != BeginningOfGame) {
2713 if (appData.autoRaiseBoard) BoardToTop();
2715 if (gamenum == -1) {
2716 newGameMode = IcsIdle;
2717 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2718 appData.getMoveList) {
2719 /* Need to get game history */
2720 ics_getting_history = H_REQUESTED;
2721 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2725 /* Initially flip the board to have black on the bottom if playing
2726 black or if the ICS flip flag is set, but let the user change
2727 it with the Flip View button. */
2728 flipView = appData.autoFlipView ?
2729 (newGameMode == IcsPlayingBlack) || ics_flip :
2732 /* Done with values from previous mode; copy in new ones */
2733 gameMode = newGameMode;
2735 ics_gamenum = gamenum;
2736 if (gamenum == gs_gamenum) {
2737 int klen = strlen(gs_kind);
2738 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2739 sprintf(str, "ICS %s", gs_kind);
2740 gameInfo.event = StrSave(str);
2742 gameInfo.event = StrSave("ICS game");
2744 gameInfo.site = StrSave(appData.icsHost);
2745 gameInfo.date = PGNDate();
2746 gameInfo.round = StrSave("-");
2747 gameInfo.white = StrSave(white);
2748 gameInfo.black = StrSave(black);
2749 timeControl = basetime * 60 * 1000;
2750 timeIncrement = increment * 1000;
2751 movesPerSession = 0;
2752 gameInfo.timeControl = TimeControlTagValue();
2753 gameInfo.variant = StringToVariant(gameInfo.event);
2755 /* Do we have the ratings? */
2756 if (strcmp(player1Name, white) == 0 &&
2757 strcmp(player2Name, black) == 0) {
2758 if (appData.debugMode)
2759 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2760 player1Rating, player2Rating);
2761 gameInfo.whiteRating = player1Rating;
2762 gameInfo.blackRating = player2Rating;
2763 } else if (strcmp(player2Name, white) == 0 &&
2764 strcmp(player1Name, black) == 0) {
2765 if (appData.debugMode)
2766 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2767 player2Rating, player1Rating);
2768 gameInfo.whiteRating = player2Rating;
2769 gameInfo.blackRating = player1Rating;
2771 player1Name[0] = player2Name[0] = NULLCHAR;
2773 /* Silence shouts if requested */
2774 if (appData.quietPlay &&
2775 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2776 SendToICS(ics_prefix);
2777 SendToICS("set shout 0\n");
2781 /* Deal with midgame name changes */
2783 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2784 if (gameInfo.white) free(gameInfo.white);
2785 gameInfo.white = StrSave(white);
2787 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2788 if (gameInfo.black) free(gameInfo.black);
2789 gameInfo.black = StrSave(black);
2793 /* Throw away game result if anything actually changes in examine mode */
2794 if (gameMode == IcsExamining && !newGame) {
2795 gameInfo.result = GameUnfinished;
2796 if (gameInfo.resultDetails != NULL) {
2797 free(gameInfo.resultDetails);
2798 gameInfo.resultDetails = NULL;
2802 /* In pausing && IcsExamining mode, we ignore boards coming
2803 in if they are in a different variation than we are. */
2804 if (pauseExamInvalid) return;
2805 if (pausing && gameMode == IcsExamining) {
2806 if (moveNum <= pauseExamForwardMostMove) {
2807 pauseExamInvalid = TRUE;
2808 forwardMostMove = pauseExamForwardMostMove;
2813 /* Parse the board */
2814 for (k = 0; k < 8; k++)
2815 for (j = 0; j < 8; j++)
2816 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2817 CopyBoard(boards[moveNum], board);
2819 startedFromSetupPosition =
2820 !CompareBoards(board, initialPosition);
2823 if (ics_getting_history == H_GOT_REQ_HEADER ||
2824 ics_getting_history == H_GOT_UNREQ_HEADER) {
2825 /* This was an initial position from a move list, not
2826 the current position */
2830 /* Update currentMove and known move number limits */
2831 newMove = newGame || moveNum > forwardMostMove;
2833 forwardMostMove = backwardMostMove = currentMove = moveNum;
2834 if (gameMode == IcsExamining && moveNum == 0) {
2835 /* Workaround for ICS limitation: we are not told the wild
2836 type when starting to examine a game. But if we ask for
2837 the move list, the move list header will tell us */
2838 ics_getting_history = H_REQUESTED;
2839 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2842 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2843 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2844 forwardMostMove = moveNum;
2845 if (!pausing || currentMove > forwardMostMove)
2846 currentMove = forwardMostMove;
2848 /* New part of history that is not contiguous with old part */
2849 if (pausing && gameMode == IcsExamining) {
2850 pauseExamInvalid = TRUE;
2851 forwardMostMove = pauseExamForwardMostMove;
2854 forwardMostMove = backwardMostMove = currentMove = moveNum;
2855 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2856 ics_getting_history = H_REQUESTED;
2857 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2862 /* Update the clocks */
2863 if (strchr(elapsed_time, '.')) {
2865 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2866 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2868 /* Time is in seconds */
2869 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2870 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2875 if (appData.zippyPlay && newGame &&
2876 gameMode != IcsObserving && gameMode != IcsIdle &&
2877 gameMode != IcsExamining)
2878 ZippyFirstBoard(moveNum, basetime, increment);
2881 /* Put the move on the move list, first converting
2882 to canonical algebraic form. */
2884 if (moveNum <= backwardMostMove) {
2885 /* We don't know what the board looked like before
2887 strcpy(parseList[moveNum - 1], move_str);
2888 strcat(parseList[moveNum - 1], " ");
2889 strcat(parseList[moveNum - 1], elapsed_time);
2890 moveList[moveNum - 1][0] = NULLCHAR;
2891 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2892 &fromX, &fromY, &toX, &toY, &promoChar)) {
2893 (void) CoordsToAlgebraic(boards[moveNum - 1],
2894 PosFlags(moveNum - 1), EP_UNKNOWN,
2895 fromY, fromX, toY, toX, promoChar,
2896 parseList[moveNum-1]);
2897 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2903 strcat(parseList[moveNum - 1], "+");
2906 strcat(parseList[moveNum - 1], "#");
2909 strcat(parseList[moveNum - 1], " ");
2910 strcat(parseList[moveNum - 1], elapsed_time);
2911 /* currentMoveString is set as a side-effect of ParseOneMove */
2912 strcpy(moveList[moveNum - 1], currentMoveString);
2913 strcat(moveList[moveNum - 1], "\n");
2914 } else if (strcmp(move_str, "none") == 0) {
2915 /* Again, we don't know what the board looked like;
2916 this is really the start of the game. */
2917 parseList[moveNum - 1][0] = NULLCHAR;
2918 moveList[moveNum - 1][0] = NULLCHAR;
2919 backwardMostMove = moveNum;
2920 startedFromSetupPosition = TRUE;
2921 fromX = fromY = toX = toY = -1;
2923 /* Move from ICS was illegal!? Punt. */
2925 if (appData.testLegality && appData.debugMode) {
2926 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2927 DisplayError(str, 0);
2930 strcpy(parseList[moveNum - 1], move_str);
2931 strcat(parseList[moveNum - 1], " ");
2932 strcat(parseList[moveNum - 1], elapsed_time);
2933 moveList[moveNum - 1][0] = NULLCHAR;
2934 fromX = fromY = toX = toY = -1;
2938 /* Send move to chess program (BEFORE animating it). */
2939 if (appData.zippyPlay && !newGame && newMove &&
2940 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2942 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2943 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2944 if (moveList[moveNum - 1][0] == NULLCHAR) {
2945 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2947 DisplayError(str, 0);
2949 if (first.sendTime) {
2950 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2952 SendMoveToProgram(moveNum - 1, &first);
2955 if (first.useColors) {
2956 SendToProgram(gameMode == IcsPlayingWhite ?
2958 "black\ngo\n", &first);
2960 SendToProgram("go\n", &first);
2962 first.maybeThinking = TRUE;
2965 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
2966 if (moveList[moveNum - 1][0] == NULLCHAR) {
2967 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
2968 DisplayError(str, 0);
2970 SendMoveToProgram(moveNum - 1, &first);
2977 if (moveNum > 0 && !gotPremove) {
2978 /* If move comes from a remote source, animate it. If it
2979 isn't remote, it will have already been animated. */
2980 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
2981 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
2983 if (!pausing && appData.highlightLastMove) {
2984 SetHighlights(fromX, fromY, toX, toY);
2988 /* Start the clocks */
2989 whiteFlag = blackFlag = FALSE;
2990 appData.clockMode = !(basetime == 0 && increment == 0);
2992 ics_clock_paused = TRUE;
2994 } else if (ticking == 1) {
2995 ics_clock_paused = FALSE;
2997 if (gameMode == IcsIdle ||
2998 relation == RELATION_OBSERVING_STATIC ||
2999 relation == RELATION_EXAMINING ||
3001 DisplayBothClocks();
3005 /* Display opponents and material strengths */
3006 if (gameInfo.variant != VariantBughouse &&
3007 gameInfo.variant != VariantCrazyhouse) {
3008 if (tinyLayout || smallLayout) {
3009 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3010 gameInfo.white, white_stren, gameInfo.black, black_stren,
3011 basetime, increment);
3013 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3014 gameInfo.white, white_stren, gameInfo.black, black_stren,
3015 basetime, increment);
3021 /* Display the board */
3024 if (appData.premove)
3026 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3027 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3028 ClearPremoveHighlights();
3030 DrawPosition(FALSE, boards[currentMove]);
3031 DisplayMove(moveNum - 1);
3032 if (appData.ringBellAfterMoves && !ics_user_moved)
3036 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3043 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3044 ics_getting_history = H_REQUESTED;
3045 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3051 AnalysisPeriodicEvent(force)
3054 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3055 && !force) || !appData.periodicUpdates)
3058 /* Send . command to Crafty to collect stats */
3059 SendToProgram(".\n", &first);
3061 /* Don't send another until we get a response (this makes
3062 us stop sending to old Crafty's which don't understand
3063 the "." command (sending illegal cmds resets node count & time,
3064 which looks bad)) */
3065 programStats.ok_to_send = 0;
3069 SendMoveToProgram(moveNum, cps)
3071 ChessProgramState *cps;
3074 if (cps->useUsermove) {
3075 SendToProgram("usermove ", cps);
3079 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3080 int len = space - parseList[moveNum];
3081 memcpy(buf, parseList[moveNum], len);
3083 buf[len] = NULLCHAR;
3085 sprintf(buf, "%s\n", parseList[moveNum]);
3087 SendToProgram(buf, cps);
3089 SendToProgram(moveList[moveNum], cps);
3094 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3096 int fromX, fromY, toX, toY;
3098 char user_move[MSG_SIZ];
3102 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3103 (int)moveType, fromX, fromY, toX, toY);
3104 DisplayError(user_move + strlen("say "), 0);
3106 case WhiteKingSideCastle:
3107 case BlackKingSideCastle:
3108 case WhiteQueenSideCastleWild:
3109 case BlackQueenSideCastleWild:
3110 sprintf(user_move, "o-o\n");
3112 case WhiteQueenSideCastle:
3113 case BlackQueenSideCastle:
3114 case WhiteKingSideCastleWild:
3115 case BlackKingSideCastleWild:
3116 sprintf(user_move, "o-o-o\n");
3118 case WhitePromotionQueen:
3119 case BlackPromotionQueen:
3120 case WhitePromotionRook:
3121 case BlackPromotionRook:
3122 case WhitePromotionBishop:
3123 case BlackPromotionBishop:
3124 case WhitePromotionKnight:
3125 case BlackPromotionKnight:
3126 case WhitePromotionKing:
3127 case BlackPromotionKing:
3128 sprintf(user_move, "%c%c%c%c=%c\n",
3129 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3130 PieceToChar(PromoPiece(moveType)));
3134 sprintf(user_move, "%c@%c%c\n",
3135 ToUpper(PieceToChar((ChessSquare) fromX)),
3136 'a' + toX, '1' + toY);
3139 case WhiteCapturesEnPassant:
3140 case BlackCapturesEnPassant:
3141 case IllegalMove: /* could be a variant we don't quite understand */
3142 sprintf(user_move, "%c%c%c%c\n",
3143 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3146 SendToICS(user_move);
3150 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3155 if (rf == DROP_RANK) {
3156 sprintf(move, "%c@%c%c\n",
3157 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3159 if (promoChar == 'x' || promoChar == NULLCHAR) {
3160 sprintf(move, "%c%c%c%c\n",
3161 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3163 sprintf(move, "%c%c%c%c%c\n",
3164 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3170 ProcessICSInitScript(f)
3175 while (fgets(buf, MSG_SIZ, f)) {
3176 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3183 /* Parser for moves from gnuchess, ICS, or user typein box */
3185 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3188 ChessMove *moveType;
3189 int *fromX, *fromY, *toX, *toY;
3192 *moveType = yylexstr(moveNum, move);
3193 switch (*moveType) {
3194 case WhitePromotionQueen:
3195 case BlackPromotionQueen:
3196 case WhitePromotionRook:
3197 case BlackPromotionRook:
3198 case WhitePromotionBishop:
3199 case BlackPromotionBishop:
3200 case WhitePromotionKnight:
3201 case BlackPromotionKnight:
3202 case WhitePromotionKing:
3203 case BlackPromotionKing:
3205 case WhiteCapturesEnPassant:
3206 case BlackCapturesEnPassant:
3207 case WhiteKingSideCastle:
3208 case WhiteQueenSideCastle:
3209 case BlackKingSideCastle:
3210 case BlackQueenSideCastle:
3211 case WhiteKingSideCastleWild:
3212 case WhiteQueenSideCastleWild:
3213 case BlackKingSideCastleWild:
3214 case BlackQueenSideCastleWild:
3215 case IllegalMove: /* bug or odd chess variant */
3216 *fromX = currentMoveString[0] - 'a';
3217 *fromY = currentMoveString[1] - '1';
3218 *toX = currentMoveString[2] - 'a';
3219 *toY = currentMoveString[3] - '1';
3220 *promoChar = currentMoveString[4];
3221 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3222 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3223 *fromX = *fromY = *toX = *toY = 0;
3226 if (appData.testLegality) {
3227 return (*moveType != IllegalMove);
3229 return !(fromX == fromY && toX == toY);
3234 *fromX = *moveType == WhiteDrop ?
3235 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3236 (int) CharToPiece(ToLower(currentMoveString[0]));
3238 *toX = currentMoveString[2] - 'a';
3239 *toY = currentMoveString[3] - '1';
3240 *promoChar = NULLCHAR;
3244 case ImpossibleMove:
3245 case (ChessMove) 0: /* end of file */
3255 *fromX = *fromY = *toX = *toY = 0;
3256 *promoChar = NULLCHAR;
3263 InitPosition(redraw)
3266 currentMove = forwardMostMove = backwardMostMove = 0;
3267 switch (gameInfo.variant) {
3269 CopyBoard(boards[0], initialPosition);
3271 case VariantTwoKings:
3272 CopyBoard(boards[0], twoKingsPosition);
3273 startedFromSetupPosition = TRUE;
3275 case VariantWildCastle:
3276 CopyBoard(boards[0], initialPosition);
3277 /* !!?shuffle with kings guaranteed to be on d or e file */
3279 case VariantNoCastle:
3280 CopyBoard(boards[0], initialPosition);
3281 /* !!?unconstrained back-rank shuffle */
3283 case VariantFischeRandom:
3284 CopyBoard(boards[0], initialPosition);
3285 /* !!shuffle according to FR rules */
3289 DrawPosition(FALSE, boards[currentMove]);
3293 SendBoard(cps, moveNum)
3294 ChessProgramState *cps;
3297 char message[MSG_SIZ];
3299 if (cps->useSetboard) {
3300 char* fen = PositionToFEN(moveNum);
3301 sprintf(message, "setboard %s\n", fen);
3302 SendToProgram(message, cps);
3308 /* Kludge to set black to move, avoiding the troublesome and now
3309 * deprecated "black" command.
3311 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3313 SendToProgram("edit\n", cps);
3314 SendToProgram("#\n", cps);
3315 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3316 bp = &boards[moveNum][i][0];
3317 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3318 if ((int) *bp < (int) BlackPawn) {
3319 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3321 SendToProgram(message, cps);
3326 SendToProgram("c\n", cps);
3327 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3328 bp = &boards[moveNum][i][0];
3329 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3330 if (((int) *bp != (int) EmptySquare)