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 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 */
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));
215 double u64ToDouble P((u64 value));
218 extern void ConsoleCreate();
221 extern int tinyLayout, smallLayout;
222 static ChessProgramStats programStats;
224 /* States for ics_getting_history */
226 #define H_REQUESTED 1
227 #define H_GOT_REQ_HEADER 2
228 #define H_GOT_UNREQ_HEADER 3
229 #define H_GETTING_MOVES 4
230 #define H_GOT_UNWANTED_HEADER 5
232 /* whosays values for GameEnds */
239 /* Maximum number of games in a cmail message */
240 #define CMAIL_MAX_GAMES 20
242 /* Different types of move when calling RegisterMove */
244 #define CMAIL_RESIGN 1
246 #define CMAIL_ACCEPT 3
248 /* Different types of result to remember for each game */
249 #define CMAIL_NOT_RESULT 0
250 #define CMAIL_OLD_RESULT 1
251 #define CMAIL_NEW_RESULT 2
253 /* Telnet protocol constants */
263 /* Some compiler can't cast u64 to double
264 * This function do the job for us:
266 * We use the highest bit for cast, this only
267 * works if the highest bit is not
268 * in use (This should not happen)
270 * We used this for all compiler
273 u64ToDouble(u64 value)
276 u64 tmp = value & 0x7fffffffffffffff;
277 r = (double)(s64)tmp;
278 if (value & 0x8000000000000000)
279 r += 9.2233720368547758080e18; /* 2^63 */
283 /* Fake up flags for now, as we aren't keeping track of castling
288 int flags = F_ALL_CASTLE_OK;
289 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
290 switch (gameInfo.variant) {
292 case VariantGiveaway:
293 flags |= F_IGNORE_CHECK;
294 flags &= ~F_ALL_CASTLE_OK;
297 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
299 case VariantKriegspiel:
300 flags |= F_KRIEGSPIEL_CAPTURE;
302 case VariantNoCastle:
303 flags &= ~F_ALL_CASTLE_OK;
311 FILE *gameFileFP, *debugFP;
313 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
314 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
315 char thinkOutput1[MSG_SIZ*10];
317 ChessProgramState first, second;
319 /* premove variables */
322 int premoveFromX = 0;
323 int premoveFromY = 0;
324 int premovePromoChar = 0;
326 Boolean alarmSounded;
327 /* end premove variables */
329 char *ics_prefix = "$";
330 int ics_type = ICS_GENERIC;
332 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
333 int pauseExamForwardMostMove = 0;
334 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
335 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
336 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
337 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
338 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
339 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
340 int whiteFlag = FALSE, blackFlag = FALSE;
341 int userOfferedDraw = FALSE;
342 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
343 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
344 int cmailMoveType[CMAIL_MAX_GAMES];
345 long ics_clock_paused = 0;
346 ProcRef icsPR = NoProc, cmailPR = NoProc;
347 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
348 GameMode gameMode = BeginningOfGame;
349 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
350 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
351 char white_holding[64], black_holding[64];
352 TimeMark lastNodeCountTime;
353 long lastNodeCount=0;
354 int have_sent_ICS_logon = 0;
356 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
357 long timeRemaining[2][MAX_MOVES];
359 TimeMark programStartTime;
360 char ics_handle[MSG_SIZ];
361 int have_set_title = 0;
363 /* animateTraining preserves the state of appData.animate
364 * when Training mode is activated. This allows the
365 * response to be animated when appData.animate == TRUE and
366 * appData.animateDragging == TRUE.
368 Boolean animateTraining;
374 Board boards[MAX_MOVES];
375 Board initialPosition = {
376 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
377 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
378 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
379 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
380 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
381 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
382 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
383 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
384 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
385 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
386 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
387 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
388 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
389 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
390 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
391 BlackKing, BlackBishop, BlackKnight, BlackRook }
393 Board twoKingsPosition = {
394 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
395 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
396 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
397 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
398 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
399 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
400 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
401 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
402 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
403 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
404 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
405 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
406 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
407 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
408 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
409 BlackKing, BlackKing, BlackKnight, BlackRook }
413 /* Convert str to a rating. Checks for special cases of "----",
414 "++++", etc. Also strips ()'s */
416 string_to_rating(str)
419 while(*str && !isdigit(*str)) ++str;
421 return 0; /* One of the special "no rating" cases */
429 /* Init programStats */
430 programStats.movelist[0] = 0;
431 programStats.depth = 0;
432 programStats.nr_moves = 0;
433 programStats.moves_left = 0;
434 programStats.nodes = 0;
435 programStats.time = 100;
436 programStats.score = 0;
437 programStats.got_only_move = 0;
438 programStats.got_fail = 0;
439 programStats.line_is_book = 0;
445 int matched, min, sec;
447 GetTimeMark(&programStartTime);
450 programStats.ok_to_send = 1;
451 programStats.seen_stat = 0;
454 * Initialize game list
460 * Internet chess server status
462 if (appData.icsActive) {
463 appData.matchMode = FALSE;
464 appData.matchGames = 0;
466 appData.noChessProgram = !appData.zippyPlay;
468 appData.zippyPlay = FALSE;
469 appData.zippyTalk = FALSE;
470 appData.noChessProgram = TRUE;
472 if (*appData.icsHelper != NULLCHAR) {
473 appData.useTelnet = TRUE;
474 appData.telnetProgram = appData.icsHelper;
477 appData.zippyTalk = appData.zippyPlay = FALSE;
481 * Parse timeControl resource
483 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
484 appData.movesPerSession)) {
486 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
487 DisplayFatalError(buf, 0, 2);
491 * Parse searchTime resource
493 if (*appData.searchTime != NULLCHAR) {
494 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
496 searchTime = min * 60;
497 } else if (matched == 2) {
498 searchTime = min * 60 + sec;
501 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
502 DisplayFatalError(buf, 0, 2);
506 first.which = "first";
507 second.which = "second";
508 first.maybeThinking = second.maybeThinking = FALSE;
509 first.pr = second.pr = NoProc;
510 first.isr = second.isr = NULL;
511 first.sendTime = second.sendTime = 2;
512 first.sendDrawOffers = 1;
513 if (appData.firstPlaysBlack) {
514 first.twoMachinesColor = "black\n";
515 second.twoMachinesColor = "white\n";
517 first.twoMachinesColor = "white\n";
518 second.twoMachinesColor = "black\n";
520 first.program = appData.firstChessProgram;
521 second.program = appData.secondChessProgram;
522 first.host = appData.firstHost;
523 second.host = appData.secondHost;
524 first.dir = appData.firstDirectory;
525 second.dir = appData.secondDirectory;
526 first.other = &second;
527 second.other = &first;
528 first.initString = appData.initString;
529 second.initString = appData.secondInitString;
530 first.computerString = appData.firstComputerString;
531 second.computerString = appData.secondComputerString;
532 first.useSigint = second.useSigint = TRUE;
533 first.useSigterm = second.useSigterm = TRUE;
534 first.reuse = appData.reuseFirst;
535 second.reuse = appData.reuseSecond;
536 first.useSetboard = second.useSetboard = FALSE;
537 first.useSAN = second.useSAN = FALSE;
538 first.usePing = second.usePing = FALSE;
539 first.lastPing = second.lastPing = 0;
540 first.lastPong = second.lastPong = 0;
541 first.usePlayother = second.usePlayother = FALSE;
542 first.useColors = second.useColors = TRUE;
543 first.useUsermove = second.useUsermove = FALSE;
544 first.sendICS = second.sendICS = FALSE;
545 first.sendName = second.sendName = appData.icsActive;
546 first.sdKludge = second.sdKludge = FALSE;
547 first.stKludge = second.stKludge = FALSE;
548 TidyProgramName(first.program, first.host, first.tidy);
549 TidyProgramName(second.program, second.host, second.tidy);
550 first.matchWins = second.matchWins = 0;
551 strcpy(first.variants, appData.variant);
552 strcpy(second.variants, appData.variant);
553 first.analysisSupport = second.analysisSupport = 2; /* detect */
554 first.analyzing = second.analyzing = FALSE;
555 first.initDone = second.initDone = FALSE;
557 if (appData.firstProtocolVersion > PROTOVER ||
558 appData.firstProtocolVersion < 1) {
560 sprintf(buf, _("protocol version %d not supported"),
561 appData.firstProtocolVersion);
562 DisplayFatalError(buf, 0, 2);
564 first.protocolVersion = appData.firstProtocolVersion;
567 if (appData.secondProtocolVersion > PROTOVER ||
568 appData.secondProtocolVersion < 1) {
570 sprintf(buf, _("protocol version %d not supported"),
571 appData.secondProtocolVersion);
572 DisplayFatalError(buf, 0, 2);
574 second.protocolVersion = appData.secondProtocolVersion;
577 if (appData.icsActive) {
578 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
579 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
580 appData.clockMode = FALSE;
581 first.sendTime = second.sendTime = 0;
585 /* Override some settings from environment variables, for backward
586 compatibility. Unfortunately it's not feasible to have the env
587 vars just set defaults, at least in xboard. Ugh.
589 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
594 if (appData.noChessProgram) {
595 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
596 + strlen(PATCHLEVEL));
597 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
601 while (*q != ' ' && *q != NULLCHAR) q++;
603 while (p > first.program && *(p-1) != '/') p--;
604 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
605 + strlen(PATCHLEVEL) + (q - p));
606 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
607 strncat(programVersion, p, q - p);
610 if (!appData.icsActive) {
612 /* Check for variants that are supported only in ICS mode,
613 or not at all. Some that are accepted here nevertheless
614 have bugs; see comments below.
616 VariantClass variant = StringToVariant(appData.variant);
618 case VariantBughouse: /* need four players and two boards */
619 case VariantKriegspiel: /* need to hide pieces and move details */
620 case VariantFischeRandom: /* castling doesn't work, shuffle not done */
621 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
622 DisplayFatalError(buf, 0, 2);
626 case VariantLoadable:
636 sprintf(buf, _("Unknown variant name %s"), appData.variant);
637 DisplayFatalError(buf, 0, 2);
640 case VariantNormal: /* definitely works! */
641 case VariantWildCastle: /* pieces not automatically shuffled */
642 case VariantNoCastle: /* pieces not automatically shuffled */
643 case VariantCrazyhouse: /* holdings not shown,
644 offboard interposition not understood */
645 case VariantLosers: /* should work except for win condition,
646 and doesn't know captures are mandatory */
647 case VariantSuicide: /* should work except for win condition,
648 and doesn't know captures are mandatory */
649 case VariantGiveaway: /* should work except for win condition,
650 and doesn't know captures are mandatory */
651 case VariantTwoKings: /* should work */
652 case VariantAtomic: /* should work except for win condition */
653 case Variant3Check: /* should work except for win condition */
654 case VariantShatranj: /* might work if TestLegality is off */
661 ParseTimeControl(tc, ti, mps)
666 int matched, min, sec;
668 matched = sscanf(tc, "%d:%d", &min, &sec);
670 timeControl = min * 60 * 1000;
671 } else if (matched == 2) {
672 timeControl = (min * 60 + sec) * 1000;
678 timeIncrement = ti * 1000; /* convert to ms */
682 movesPerSession = mps;
690 if (appData.debugMode) {
691 fprintf(debugFP, "%s\n", programVersion);
694 if (appData.matchGames > 0) {
695 appData.matchMode = TRUE;
696 } else if (appData.matchMode) {
697 appData.matchGames = 1;
700 if (appData.noChessProgram || first.protocolVersion == 1) {
703 /* kludge: allow timeout for initial "feature" commands */
705 DisplayMessage("", "Starting chess program");
706 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
711 InitBackEnd3 P((void))
713 GameMode initialMode;
717 InitChessProgram(&first);
720 /* Make a console window if needed */
721 if (appData.icsActive) ConsoleCreate();
724 if (appData.icsActive) {
727 if (*appData.icsCommPort != NULLCHAR) {
728 sprintf(buf, _("Could not open comm port %s"),
729 appData.icsCommPort);
731 sprintf(buf, _("Could not connect to host %s, port %s"),
732 appData.icsHost, appData.icsPort);
734 DisplayFatalError(buf, err, 1);
739 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
741 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
742 } else if (appData.noChessProgram) {
748 if (*appData.cmailGameName != NULLCHAR) {
750 OpenLoopback(&cmailPR);
752 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
756 DisplayMessage("", "");
757 if (StrCaseCmp(appData.initialMode, "") == 0) {
758 initialMode = BeginningOfGame;
759 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
760 initialMode = TwoMachinesPlay;
761 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
762 initialMode = AnalyzeFile;
763 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
764 initialMode = AnalyzeMode;
765 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
766 initialMode = MachinePlaysWhite;
767 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
768 initialMode = MachinePlaysBlack;
769 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
770 initialMode = EditGame;
771 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
772 initialMode = EditPosition;
773 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
774 initialMode = Training;
776 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
777 DisplayFatalError(buf, 0, 2);
781 if (appData.matchMode) {
782 /* Set up machine vs. machine match */
783 if (appData.noChessProgram) {
784 DisplayFatalError(_("Can't have a match with no chess programs"),
790 if (*appData.loadGameFile != NULLCHAR) {
791 if (!LoadGameFromFile(appData.loadGameFile,
792 appData.loadGameIndex,
793 appData.loadGameFile, FALSE)) {
794 DisplayFatalError(_("Bad game file"), 0, 1);
797 } else if (*appData.loadPositionFile != NULLCHAR) {
798 if (!LoadPositionFromFile(appData.loadPositionFile,
799 appData.loadPositionIndex,
800 appData.loadPositionFile)) {
801 DisplayFatalError(_("Bad position file"), 0, 1);
806 } else if (*appData.cmailGameName != NULLCHAR) {
807 /* Set up cmail mode */
808 ReloadCmailMsgEvent(TRUE);
810 /* Set up other modes */
811 if (initialMode == AnalyzeFile) {
812 if (*appData.loadGameFile == NULLCHAR) {
813 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
817 if (*appData.loadGameFile != NULLCHAR) {
818 (void) LoadGameFromFile(appData.loadGameFile,
819 appData.loadGameIndex,
820 appData.loadGameFile, TRUE);
821 } else if (*appData.loadPositionFile != NULLCHAR) {
822 (void) LoadPositionFromFile(appData.loadPositionFile,
823 appData.loadPositionIndex,
824 appData.loadPositionFile);
826 if (initialMode == AnalyzeMode) {
827 if (appData.noChessProgram) {
828 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
831 if (appData.icsActive) {
832 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
836 } else if (initialMode == AnalyzeFile) {
837 ShowThinkingEvent(TRUE);
839 AnalysisPeriodicEvent(1);
840 } else if (initialMode == MachinePlaysWhite) {
841 if (appData.noChessProgram) {
842 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
846 if (appData.icsActive) {
847 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
852 } else if (initialMode == MachinePlaysBlack) {
853 if (appData.noChessProgram) {
854 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
858 if (appData.icsActive) {
859 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
864 } else if (initialMode == TwoMachinesPlay) {
865 if (appData.noChessProgram) {
866 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
870 if (appData.icsActive) {
871 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
876 } else if (initialMode == EditGame) {
878 } else if (initialMode == EditPosition) {
880 } else if (initialMode == Training) {
881 if (*appData.loadGameFile == NULLCHAR) {
882 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
891 * Establish will establish a contact to a remote host.port.
892 * Sets icsPR to a ProcRef for a process (or pseudo-process)
893 * used to talk to the host.
894 * Returns 0 if okay, error code if not.
901 if (*appData.icsCommPort != NULLCHAR) {
902 /* Talk to the host through a serial comm port */
903 return OpenCommPort(appData.icsCommPort, &icsPR);
905 } else if (*appData.gateway != NULLCHAR) {
906 if (*appData.remoteShell == NULLCHAR) {
907 /* Use the rcmd protocol to run telnet program on a gateway host */
908 sprintf(buf, "%s %s %s",
909 appData.telnetProgram, appData.icsHost, appData.icsPort);
910 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
913 /* Use the rsh program to run telnet program on a gateway host */
914 if (*appData.remoteUser == NULLCHAR) {
915 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
916 appData.gateway, appData.telnetProgram,
917 appData.icsHost, appData.icsPort);
919 sprintf(buf, "%s %s -l %s %s %s %s",
920 appData.remoteShell, appData.gateway,
921 appData.remoteUser, appData.telnetProgram,
922 appData.icsHost, appData.icsPort);
924 return StartChildProcess(buf, "", &icsPR);
927 } else if (appData.useTelnet) {
928 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
931 /* TCP socket interface differs somewhat between
932 Unix and NT; handle details in the front end.
934 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
939 show_bytes(fp, buf, count)
945 if (*buf < 040 || *(unsigned char *) buf > 0177) {
946 fprintf(fp, "\\%03o", *buf & 0xff);
955 /* Returns an errno value */
957 OutputMaybeTelnet(pr, message, count, outError)
963 char buf[8192], *p, *q, *buflim;
964 int left, newcount, outcount;
966 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
967 *appData.gateway != NULLCHAR) {
968 if (appData.debugMode) {
969 fprintf(debugFP, ">ICS: ");
970 show_bytes(debugFP, message, count);
971 fprintf(debugFP, "\n");
973 return OutputToProcess(pr, message, count, outError);
976 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
983 if (appData.debugMode) {
984 fprintf(debugFP, ">ICS: ");
985 show_bytes(debugFP, buf, newcount);
986 fprintf(debugFP, "\n");
988 outcount = OutputToProcess(pr, buf, newcount, outError);
989 if (outcount < newcount) return -1; /* to be sure */
996 } else if (((unsigned char) *p) == TN_IAC) {
997 *q++ = (char) TN_IAC;
1004 if (appData.debugMode) {
1005 fprintf(debugFP, ">ICS: ");
1006 show_bytes(debugFP, buf, newcount);
1007 fprintf(debugFP, "\n");
1009 outcount = OutputToProcess(pr, buf, newcount, outError);
1010 if (outcount < newcount) return -1; /* to be sure */
1015 read_from_player(isr, closure, message, count, error)
1022 int outError, outCount;
1023 static int gotEof = 0;
1025 /* Pass data read from player on to ICS */
1028 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1029 if (outCount < count) {
1030 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1032 } else if (count < 0) {
1033 RemoveInputSource(isr);
1034 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1035 } else if (gotEof++ > 0) {
1036 RemoveInputSource(isr);
1037 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1045 int count, outCount, outError;
1047 if (icsPR == NULL) return;
1050 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1051 if (outCount < count) {
1052 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1056 /* This is used for sending logon scripts to the ICS. Sending
1057 without a delay causes problems when using timestamp on ICC
1058 (at least on my machine). */
1060 SendToICSDelayed(s,msdelay)
1064 int count, outCount, outError;
1066 if (icsPR == NULL) return;
1069 if (appData.debugMode) {
1070 fprintf(debugFP, ">ICS: ");
1071 show_bytes(debugFP, s, count);
1072 fprintf(debugFP, "\n");
1074 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1076 if (outCount < count) {
1077 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1082 /* Remove all highlighting escape sequences in s
1083 Also deletes any suffix starting with '('
1086 StripHighlightAndTitle(s)
1089 static char retbuf[MSG_SIZ];
1092 while (*s != NULLCHAR) {
1093 while (*s == '\033') {
1094 while (*s != NULLCHAR && !isalpha(*s)) s++;
1095 if (*s != NULLCHAR) s++;
1097 while (*s != NULLCHAR && *s != '\033') {
1098 if (*s == '(' || *s == '[') {
1109 /* Remove all highlighting escape sequences in s */
1114 static char retbuf[MSG_SIZ];
1117 while (*s != NULLCHAR) {
1118 while (*s == '\033') {
1119 while (*s != NULLCHAR && !isalpha(*s)) s++;
1120 if (*s != NULLCHAR) s++;
1122 while (*s != NULLCHAR && *s != '\033') {
1130 char *variantNames[] = VARIANT_NAMES;
1135 return variantNames[v];
1139 /* Identify a variant from the strings the chess servers use or the
1140 PGN Variant tag names we use. */
1147 VariantClass v = VariantNormal;
1148 int i, found = FALSE;
1153 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1154 if (StrCaseStr(e, variantNames[i])) {
1155 v = (VariantClass) i;
1162 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1163 || StrCaseStr(e, "wild/fr")) {
1164 v = VariantFischeRandom;
1165 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1166 (i = 1, p = StrCaseStr(e, "w"))) {
1168 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1175 case 0: /* FICS only, actually */
1177 /* Castling legal even if K starts on d-file */
1178 v = VariantWildCastle;
1183 /* Castling illegal even if K & R happen to start in
1184 normal positions. */
1185 v = VariantNoCastle;
1198 /* Castling legal iff K & R start in normal positions */
1204 /* Special wilds for position setup; unclear what to do here */
1205 v = VariantLoadable;
1208 /* Bizarre ICC game */
1209 v = VariantTwoKings;
1212 v = VariantKriegspiel;
1218 v = VariantFischeRandom;
1221 v = VariantCrazyhouse;
1224 v = VariantBughouse;
1230 /* Not quite the same as FICS suicide! */
1231 v = VariantGiveaway;
1237 v = VariantShatranj;
1240 /* Temporary names for future ICC types. The name *will* change in
1241 the next xboard/WinBoard release after ICC defines it. */
1268 /* Found "wild" or "w" in the string but no number;
1269 must assume it's normal chess. */
1273 sprintf(buf, "Unknown wild type %d", wnum);
1274 DisplayError(buf, 0);
1280 if (appData.debugMode) {
1281 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1282 e, wnum, VariantName(v));
1287 static int leftover_start = 0, leftover_len = 0;
1288 char star_match[STAR_MATCH_N][MSG_SIZ];
1290 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1291 advance *index beyond it, and set leftover_start to the new value of
1292 *index; else return FALSE. If pattern contains the character '*', it
1293 matches any sequence of characters not containing '\r', '\n', or the
1294 character following the '*' (if any), and the matched sequence(s) are
1295 copied into star_match.
1298 looking_at(buf, index, pattern)
1303 char *bufp = &buf[*index], *patternp = pattern;
1305 char *matchp = star_match[0];
1308 if (*patternp == NULLCHAR) {
1309 *index = leftover_start = bufp - buf;
1313 if (*bufp == NULLCHAR) return FALSE;
1314 if (*patternp == '*') {
1315 if (*bufp == *(patternp + 1)) {
1317 matchp = star_match[++star_count];
1321 } else if (*bufp == '\n' || *bufp == '\r') {
1323 if (*patternp == NULLCHAR)
1328 *matchp++ = *bufp++;
1332 if (*patternp != *bufp) return FALSE;
1339 SendToPlayer(data, length)
1343 int error, outCount;
1344 outCount = OutputToProcess(NoProc, data, length, &error);
1345 if (outCount < length) {
1346 DisplayFatalError(_("Error writing to display"), error, 1);
1351 PackHolding(packed, holding)
1363 switch (runlength) {
1374 sprintf(q, "%d", runlength);
1386 /* Telnet protocol requests from the front end */
1388 TelnetRequest(ddww, option)
1389 unsigned char ddww, option;
1391 unsigned char msg[3];
1392 int outCount, outError;
1394 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1396 if (appData.debugMode) {
1397 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1413 sprintf(buf1, "%d", ddww);
1422 sprintf(buf2, "%d", option);
1425 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1430 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1432 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1439 if (!appData.icsActive) return;
1440 TelnetRequest(TN_DO, TN_ECHO);
1446 if (!appData.icsActive) return;
1447 TelnetRequest(TN_DONT, TN_ECHO);
1450 static int loggedOn = FALSE;
1452 /*-- Game start info cache: --*/
1454 char gs_kind[MSG_SIZ];
1455 static char player1Name[128] = "";
1456 static char player2Name[128] = "";
1457 static int player1Rating = -1;
1458 static int player2Rating = -1;
1459 /*----------------------------*/
1462 read_from_ics(isr, closure, data, count, error)
1469 #define BUF_SIZE 8192
1470 #define STARTED_NONE 0
1471 #define STARTED_MOVES 1
1472 #define STARTED_BOARD 2
1473 #define STARTED_OBSERVE 3
1474 #define STARTED_HOLDINGS 4
1475 #define STARTED_CHATTER 5
1476 #define STARTED_COMMENT 6
1477 #define STARTED_MOVES_NOHIDE 7
1479 static int started = STARTED_NONE;
1480 static char parse[20000];
1481 static int parse_pos = 0;
1482 static char buf[BUF_SIZE + 1];
1483 static int firstTime = TRUE, intfSet = FALSE;
1484 static ColorClass curColor = ColorNormal;
1485 static ColorClass prevColor = ColorNormal;
1486 static int savingComment = FALSE;
1493 /* For zippy color lines of winboard
1494 * cleanup for gcc compiler */
1500 if (appData.debugMode) {
1502 fprintf(debugFP, "<ICS: ");
1503 show_bytes(debugFP, data, count);
1504 fprintf(debugFP, "\n");
1510 /* If last read ended with a partial line that we couldn't parse,
1511 prepend it to the new read and try again. */
1512 if (leftover_len > 0) {
1513 for (i=0; i<leftover_len; i++)
1514 buf[i] = buf[leftover_start + i];
1517 /* Copy in new characters, removing nulls and \r's */
1518 buf_len = leftover_len;
1519 for (i = 0; i < count; i++) {
1520 if (data[i] != NULLCHAR && data[i] != '\r')
1521 buf[buf_len++] = data[i];
1524 buf[buf_len] = NULLCHAR;
1525 next_out = leftover_len;
1529 while (i < buf_len) {
1530 /* Deal with part of the TELNET option negotiation
1531 protocol. We refuse to do anything beyond the
1532 defaults, except that we allow the WILL ECHO option,
1533 which ICS uses to turn off password echoing when we are
1534 directly connected to it. We reject this option
1535 if localLineEditing mode is on (always on in xboard)
1536 and we are talking to port 23, which might be a real
1537 telnet server that will try to keep WILL ECHO on permanently.
1539 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1540 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1541 unsigned char option;
1543 switch ((unsigned char) buf[++i]) {
1545 if (appData.debugMode)
1546 fprintf(debugFP, "\n<WILL ");
1547 switch (option = (unsigned char) buf[++i]) {
1549 if (appData.debugMode)
1550 fprintf(debugFP, "ECHO ");
1551 /* Reply only if this is a change, according
1552 to the protocol rules. */
1553 if (remoteEchoOption) break;
1554 if (appData.localLineEditing &&
1555 atoi(appData.icsPort) == TN_PORT) {
1556 TelnetRequest(TN_DONT, TN_ECHO);
1559 TelnetRequest(TN_DO, TN_ECHO);
1560 remoteEchoOption = TRUE;
1564 if (appData.debugMode)
1565 fprintf(debugFP, "%d ", option);
1566 /* Whatever this is, we don't want it. */
1567 TelnetRequest(TN_DONT, option);
1572 if (appData.debugMode)
1573 fprintf(debugFP, "\n<WONT ");
1574 switch (option = (unsigned char) buf[++i]) {
1576 if (appData.debugMode)
1577 fprintf(debugFP, "ECHO ");
1578 /* Reply only if this is a change, according
1579 to the protocol rules. */
1580 if (!remoteEchoOption) break;
1582 TelnetRequest(TN_DONT, TN_ECHO);
1583 remoteEchoOption = FALSE;
1586 if (appData.debugMode)
1587 fprintf(debugFP, "%d ", (unsigned char) option);
1588 /* Whatever this is, it must already be turned
1589 off, because we never agree to turn on
1590 anything non-default, so according to the
1591 protocol rules, we don't reply. */
1596 if (appData.debugMode)
1597 fprintf(debugFP, "\n<DO ");
1598 switch (option = (unsigned char) buf[++i]) {
1600 /* Whatever this is, we refuse to do it. */
1601 if (appData.debugMode)
1602 fprintf(debugFP, "%d ", option);
1603 TelnetRequest(TN_WONT, option);
1608 if (appData.debugMode)
1609 fprintf(debugFP, "\n<DONT ");
1610 switch (option = (unsigned char) buf[++i]) {
1612 if (appData.debugMode)
1613 fprintf(debugFP, "%d ", option);
1614 /* Whatever this is, we are already not doing
1615 it, because we never agree to do anything
1616 non-default, so according to the protocol
1617 rules, we don't reply. */
1622 if (appData.debugMode)
1623 fprintf(debugFP, "\n<IAC ");
1624 /* Doubled IAC; pass it through */
1628 if (appData.debugMode)
1629 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1630 /* Drop all other telnet commands on the floor */
1633 if (oldi > next_out)
1634 SendToPlayer(&buf[next_out], oldi - next_out);
1640 /* OK, this at least will *usually* work */
1641 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1645 if (loggedOn && !intfSet) {
1646 if (ics_type == ICS_ICC) {
1648 "/set-quietly interface %s\n/set-quietly style 12\n",
1651 } else if (ics_type == ICS_CHESSNET) {
1652 sprintf(str, "/style 12\n");
1654 strcpy(str, "alias $ @\n$set interface ");
1655 strcat(str, programVersion);
1656 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1658 strcat(str, "$iset nohighlight 1\n");
1660 strcat(str, "$iset lock 1\n$style 12\n");
1666 if (started == STARTED_COMMENT) {
1667 /* Accumulate characters in comment */
1668 parse[parse_pos++] = buf[i];
1669 if (buf[i] == '\n') {
1670 parse[parse_pos] = NULLCHAR;
1671 AppendComment(forwardMostMove, StripHighlight(parse));
1672 started = STARTED_NONE;
1674 /* Don't match patterns against characters in chatter */
1679 if (started == STARTED_CHATTER) {
1680 if (buf[i] != '\n') {
1681 /* Don't match patterns against characters in chatter */
1685 started = STARTED_NONE;
1688 /* Kludge to deal with rcmd protocol */
1689 if (firstTime && looking_at(buf, &i, "\001*")) {
1690 DisplayFatalError(&buf[1], 0, 1);
1696 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1699 if (appData.debugMode)
1700 fprintf(debugFP, "ics_type %d\n", ics_type);
1703 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1704 ics_type = ICS_FICS;
1706 if (appData.debugMode)
1707 fprintf(debugFP, "ics_type %d\n", ics_type);
1710 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1711 ics_type = ICS_CHESSNET;
1713 if (appData.debugMode)
1714 fprintf(debugFP, "ics_type %d\n", ics_type);
1719 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1720 looking_at(buf, &i, "Logging you in as \"*\"") ||
1721 looking_at(buf, &i, "will be \"*\""))) {
1722 strcpy(ics_handle, star_match[0]);
1726 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1728 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1729 DisplayIcsInteractionTitle(buf);
1730 have_set_title = TRUE;
1733 /* skip finger notes */
1734 if (started == STARTED_NONE &&
1735 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1736 (buf[i] == '1' && buf[i+1] == '0')) &&
1737 buf[i+2] == ':' && buf[i+3] == ' ') {
1738 started = STARTED_CHATTER;
1743 /* skip formula vars */
1744 if (started == STARTED_NONE &&
1745 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1746 started = STARTED_CHATTER;
1752 if (appData.zippyTalk || appData.zippyPlay) {
1755 /* Backup address for color zippy lines */
1757 if (loggedOn == TRUE)
1758 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
1759 (appData.zippyPlay && ZippyMatch(buf, &backup)));
1761 if (ZippyControl(buf, &i) ||
1762 ZippyConverse(buf, &i) ||
1763 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1770 if (/* Don't color "message" or "messages" output */
1771 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1772 looking_at(buf, &i, "*. * at *:*: ") ||
1773 looking_at(buf, &i, "--* (*:*): ") ||
1774 /* Regular tells and says */
1775 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1776 looking_at(buf, &i, "* (your partner) tells you: ") ||
1777 looking_at(buf, &i, "* says: ") ||
1778 /* Message notifications (same color as tells) */
1779 looking_at(buf, &i, "* has left a message ") ||
1780 looking_at(buf, &i, "* just sent you a message:\n") ||
1781 /* Whispers and kibitzes */
1782 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1783 looking_at(buf, &i, "* kibitzes: ") ||
1785 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1787 if (tkind == 1 && strchr(star_match[0], ':')) {
1788 /* Avoid "tells you:" spoofs in channels */
1791 if (star_match[0][0] == NULLCHAR ||
1792 strchr(star_match[0], ' ') ||
1793 (tkind == 3 && strchr(star_match[1], ' '))) {
1794 /* Reject bogus matches */
1797 if (appData.colorize) {
1798 if (oldi > next_out) {
1799 SendToPlayer(&buf[next_out], oldi - next_out);
1804 Colorize(ColorTell, FALSE);
1805 curColor = ColorTell;
1808 Colorize(ColorKibitz, FALSE);
1809 curColor = ColorKibitz;
1812 p = strrchr(star_match[1], '(');
1819 Colorize(ColorChannel1, FALSE);
1820 curColor = ColorChannel1;
1822 Colorize(ColorChannel, FALSE);
1823 curColor = ColorChannel;
1827 curColor = ColorNormal;
1831 if (started == STARTED_NONE && appData.autoComment &&
1832 (gameMode == IcsObserving ||
1833 gameMode == IcsPlayingWhite ||
1834 gameMode == IcsPlayingBlack)) {
1835 parse_pos = i - oldi;
1836 memcpy(parse, &buf[oldi], parse_pos);
1837 parse[parse_pos] = NULLCHAR;
1838 started = STARTED_COMMENT;
1839 savingComment = TRUE;
1841 started = STARTED_CHATTER;
1842 savingComment = FALSE;
1849 if (looking_at(buf, &i, "* s-shouts: ") ||
1850 looking_at(buf, &i, "* c-shouts: ")) {
1851 if (appData.colorize) {
1852 if (oldi > next_out) {
1853 SendToPlayer(&buf[next_out], oldi - next_out);
1856 Colorize(ColorSShout, FALSE);
1857 curColor = ColorSShout;
1860 started = STARTED_CHATTER;
1864 if (looking_at(buf, &i, "--->")) {
1869 if (looking_at(buf, &i, "* shouts: ") ||
1870 looking_at(buf, &i, "--> ")) {
1871 if (appData.colorize) {
1872 if (oldi > next_out) {
1873 SendToPlayer(&buf[next_out], oldi - next_out);
1876 Colorize(ColorShout, FALSE);
1877 curColor = ColorShout;
1880 started = STARTED_CHATTER;
1884 if (looking_at( buf, &i, "Challenge:")) {
1885 if (appData.colorize) {
1886 if (oldi > next_out) {
1887 SendToPlayer(&buf[next_out], oldi - next_out);
1890 Colorize(ColorChallenge, FALSE);
1891 curColor = ColorChallenge;
1897 if (looking_at(buf, &i, "* offers you") ||
1898 looking_at(buf, &i, "* offers to be") ||
1899 looking_at(buf, &i, "* would like to") ||
1900 looking_at(buf, &i, "* requests to") ||
1901 looking_at(buf, &i, "Your opponent offers") ||
1902 looking_at(buf, &i, "Your opponent requests")) {
1904 if (appData.colorize) {
1905 if (oldi > next_out) {
1906 SendToPlayer(&buf[next_out], oldi - next_out);
1909 Colorize(ColorRequest, FALSE);
1910 curColor = ColorRequest;
1915 if (looking_at(buf, &i, "* (*) seeking")) {
1916 if (appData.colorize) {
1917 if (oldi > next_out) {
1918 SendToPlayer(&buf[next_out], oldi - next_out);
1921 Colorize(ColorSeek, FALSE);
1922 curColor = ColorSeek;
1927 if (looking_at(buf, &i, "\\ ")) {
1928 if (prevColor != ColorNormal) {
1929 if (oldi > next_out) {
1930 SendToPlayer(&buf[next_out], oldi - next_out);
1933 Colorize(prevColor, TRUE);
1934 curColor = prevColor;
1936 if (savingComment) {
1937 parse_pos = i - oldi;
1938 memcpy(parse, &buf[oldi], parse_pos);
1939 parse[parse_pos] = NULLCHAR;
1940 started = STARTED_COMMENT;
1942 started = STARTED_CHATTER;
1947 if (looking_at(buf, &i, "Black Strength :") ||
1948 looking_at(buf, &i, "<<< style 10 board >>>") ||
1949 looking_at(buf, &i, "<10>") ||
1950 looking_at(buf, &i, "#@#")) {
1951 /* Wrong board style */
1953 SendToICS(ics_prefix);
1954 SendToICS("set style 12\n");
1955 SendToICS(ics_prefix);
1956 SendToICS("refresh\n");
1960 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1962 have_sent_ICS_logon = 1;
1966 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1967 (looking_at(buf, &i, "\n<12> ") ||
1968 looking_at(buf, &i, "<12> "))) {
1970 if (oldi > next_out) {
1971 SendToPlayer(&buf[next_out], oldi - next_out);
1974 started = STARTED_BOARD;
1979 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1980 looking_at(buf, &i, "<b1> ")) {
1981 if (oldi > next_out) {
1982 SendToPlayer(&buf[next_out], oldi - next_out);
1985 started = STARTED_HOLDINGS;
1990 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1992 /* Header for a move list -- first line */
1994 switch (ics_getting_history) {
1998 case BeginningOfGame:
1999 /* User typed "moves" or "oldmoves" while we
2000 were idle. Pretend we asked for these
2001 moves and soak them up so user can step
2002 through them and/or save them.
2005 gameMode = IcsObserving;
2008 ics_getting_history = H_GOT_UNREQ_HEADER;
2010 case EditGame: /*?*/
2011 case EditPosition: /*?*/
2012 /* Should above feature work in these modes too? */
2013 /* For now it doesn't */
2014 ics_getting_history = H_GOT_UNWANTED_HEADER;
2017 ics_getting_history = H_GOT_UNWANTED_HEADER;
2022 /* Is this the right one? */
2023 if (gameInfo.white && gameInfo.black &&
2024 strcmp(gameInfo.white, star_match[0]) == 0 &&
2025 strcmp(gameInfo.black, star_match[2]) == 0) {
2027 ics_getting_history = H_GOT_REQ_HEADER;
2030 case H_GOT_REQ_HEADER:
2031 case H_GOT_UNREQ_HEADER:
2032 case H_GOT_UNWANTED_HEADER:
2033 case H_GETTING_MOVES:
2034 /* Should not happen */
2035 DisplayError(_("Error gathering move list: two headers"), 0);
2036 ics_getting_history = H_FALSE;
2040 /* Save player ratings into gameInfo if needed */
2041 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2042 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2043 (gameInfo.whiteRating == -1 ||
2044 gameInfo.blackRating == -1)) {
2046 gameInfo.whiteRating = string_to_rating(star_match[1]);
2047 gameInfo.blackRating = string_to_rating(star_match[3]);
2048 if (appData.debugMode)
2049 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2050 gameInfo.whiteRating, gameInfo.blackRating);
2055 if (looking_at(buf, &i,
2056 "* * match, initial time: * minute*, increment: * second")) {
2057 /* Header for a move list -- second line */
2058 /* Initial board will follow if this is a wild game */
2060 if (gameInfo.event != NULL) free(gameInfo.event);
2061 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2062 gameInfo.event = StrSave(str);
2063 gameInfo.variant = StringToVariant(gameInfo.event);
2067 if (looking_at(buf, &i, "Move ")) {
2068 /* Beginning of a move list */
2069 switch (ics_getting_history) {
2071 /* Normally should not happen */
2072 /* Maybe user hit reset while we were parsing */
2075 /* Happens if we are ignoring a move list that is not
2076 * the one we just requested. Common if the user
2077 * tries to observe two games without turning off
2080 case H_GETTING_MOVES:
2081 /* Should not happen */
2082 DisplayError(_("Error gathering move list: nested"), 0);
2083 ics_getting_history = H_FALSE;
2085 case H_GOT_REQ_HEADER:
2086 ics_getting_history = H_GETTING_MOVES;
2087 started = STARTED_MOVES;
2089 if (oldi > next_out) {
2090 SendToPlayer(&buf[next_out], oldi - next_out);
2093 case H_GOT_UNREQ_HEADER:
2094 ics_getting_history = H_GETTING_MOVES;
2095 started = STARTED_MOVES_NOHIDE;
2098 case H_GOT_UNWANTED_HEADER:
2099 ics_getting_history = H_FALSE;
2105 if (looking_at(buf, &i, "% ") ||
2106 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2107 && looking_at(buf, &i, "}*"))) {
2108 savingComment = FALSE;
2111 case STARTED_MOVES_NOHIDE:
2112 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2113 parse[parse_pos + i - oldi] = NULLCHAR;
2114 ParseGameHistory(parse);
2116 if (appData.zippyPlay && first.initDone) {
2117 FeedMovesToProgram(&first, forwardMostMove);
2118 if (gameMode == IcsPlayingWhite) {
2119 if (WhiteOnMove(forwardMostMove)) {
2120 if (first.sendTime) {
2121 if (first.useColors) {
2122 SendToProgram("black\n", &first);
2124 SendTimeRemaining(&first, TRUE);
2126 if (first.useColors) {
2127 SendToProgram("white\ngo\n", &first);
2129 SendToProgram("go\n", &first);
2131 first.maybeThinking = TRUE;
2133 if (first.usePlayother) {
2134 if (first.sendTime) {
2135 SendTimeRemaining(&first, TRUE);
2137 SendToProgram("playother\n", &first);
2143 } else if (gameMode == IcsPlayingBlack) {
2144 if (!WhiteOnMove(forwardMostMove)) {
2145 if (first.sendTime) {
2146 if (first.useColors) {
2147 SendToProgram("white\n", &first);
2149 SendTimeRemaining(&first, FALSE);
2151 if (first.useColors) {
2152 SendToProgram("black\ngo\n", &first);
2154 SendToProgram("go\n", &first);
2156 first.maybeThinking = TRUE;
2158 if (first.usePlayother) {
2159 if (first.sendTime) {
2160 SendTimeRemaining(&first, FALSE);
2162 SendToProgram("playother\n", &first);
2171 if (gameMode == IcsObserving && ics_gamenum == -1) {
2172 /* Moves came from oldmoves or moves command
2173 while we weren't doing anything else.
2175 currentMove = forwardMostMove;
2176 ClearHighlights();/*!!could figure this out*/
2177 flipView = appData.flipView;
2178 DrawPosition(FALSE, boards[currentMove]);
2179 DisplayBothClocks();
2180 sprintf(str, "%s vs. %s",
2181 gameInfo.white, gameInfo.black);
2185 /* Moves were history of an active game */
2186 if (gameInfo.resultDetails != NULL) {
2187 free(gameInfo.resultDetails);
2188 gameInfo.resultDetails = NULL;
2191 HistorySet(parseList, backwardMostMove,
2192 forwardMostMove, currentMove-1);
2193 DisplayMove(currentMove - 1);
2194 if (started == STARTED_MOVES) next_out = i;
2195 started = STARTED_NONE;
2196 ics_getting_history = H_FALSE;
2199 case STARTED_OBSERVE:
2200 started = STARTED_NONE;
2201 SendToICS(ics_prefix);
2202 SendToICS("refresh\n");
2211 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2212 started == STARTED_HOLDINGS ||
2213 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2214 /* Accumulate characters in move list or board */
2215 parse[parse_pos++] = buf[i];
2218 /* Start of game messages. Mostly we detect start of game
2219 when the first board image arrives. On some versions
2220 of the ICS, though, we need to do a "refresh" after starting
2221 to observe in order to get the current board right away. */
2222 if (looking_at(buf, &i, "Adding game * to observation list")) {
2223 started = STARTED_OBSERVE;
2227 /* Handle auto-observe */
2228 if (appData.autoObserve &&
2229 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2230 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2232 /* Choose the player that was highlighted, if any. */
2233 if (star_match[0][0] == '\033' ||
2234 star_match[1][0] != '\033') {
2235 player = star_match[0];
2237 player = star_match[2];
2239 sprintf(str, "%sobserve %s\n",
2240 ics_prefix, StripHighlightAndTitle(player));
2243 /* Save ratings from notify string */
2244 strcpy(player1Name, star_match[0]);
2245 player1Rating = string_to_rating(star_match[1]);
2246 strcpy(player2Name, star_match[2]);
2247 player2Rating = string_to_rating(star_match[3]);
2249 if (appData.debugMode)
2251 "Ratings from 'Game notification:' %s %d, %s %d\n",
2252 player1Name, player1Rating,
2253 player2Name, player2Rating);
2258 /* Deal with automatic examine mode after a game,
2259 and with IcsObserving -> IcsExamining transition */
2260 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2261 looking_at(buf, &i, "has made you an examiner of game *")) {
2263 int gamenum = atoi(star_match[0]);
2264 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2265 gamenum == ics_gamenum) {
2266 /* We were already playing or observing this game;
2267 no need to refetch history */
2268 gameMode = IcsExamining;
2270 pauseExamForwardMostMove = forwardMostMove;
2271 } else if (currentMove < forwardMostMove) {
2272 ForwardInner(forwardMostMove);
2275 /* I don't think this case really can happen */
2276 SendToICS(ics_prefix);
2277 SendToICS("refresh\n");
2282 /* Error messages */
2283 if (ics_user_moved) {
2284 if (looking_at(buf, &i, "Illegal move") ||
2285 looking_at(buf, &i, "Not a legal move") ||
2286 looking_at(buf, &i, "Your king is in check") ||
2287 looking_at(buf, &i, "It isn't your turn") ||
2288 looking_at(buf, &i, "It is not your move")) {
2291 if (forwardMostMove > backwardMostMove) {
2292 currentMove = --forwardMostMove;
2293 DisplayMove(currentMove - 1); /* before DMError */
2294 DisplayMoveError("Illegal move (rejected by ICS)");
2295 DrawPosition(FALSE, boards[currentMove]);
2297 DisplayBothClocks();
2303 if (looking_at(buf, &i, "still have time") ||
2304 looking_at(buf, &i, "not out of time") ||
2305 looking_at(buf, &i, "either player is out of time") ||
2306 looking_at(buf, &i, "has timeseal; checking")) {
2307 /* We must have called his flag a little too soon */
2308 whiteFlag = blackFlag = FALSE;
2312 if (looking_at(buf, &i, "added * seconds to") ||
2313 looking_at(buf, &i, "seconds were added to")) {
2314 /* Update the clocks */
2315 SendToICS(ics_prefix);
2316 SendToICS("refresh\n");
2320 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2321 ics_clock_paused = TRUE;
2326 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2327 ics_clock_paused = FALSE;
2332 /* Grab player ratings from the Creating: message.
2333 Note we have to check for the special case when
2334 the ICS inserts things like [white] or [black]. */
2335 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2336 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2338 0 player 1 name (not necessarily white)
2340 2 empty, white, or black (IGNORED)
2341 3 player 2 name (not necessarily black)
2344 The names/ratings are sorted out when the game
2345 actually starts (below).
2347 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2348 player1Rating = string_to_rating(star_match[1]);
2349 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2350 player2Rating = string_to_rating(star_match[4]);
2352 if (appData.debugMode)
2354 "Ratings from 'Creating:' %s %d, %s %d\n",
2355 player1Name, player1Rating,
2356 player2Name, player2Rating);
2361 /* Improved generic start/end-of-game messages */
2362 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2363 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2364 /* If tkind == 0: */
2365 /* star_match[0] is the game number */
2366 /* [1] is the white player's name */
2367 /* [2] is the black player's name */
2368 /* For end-of-game: */
2369 /* [3] is the reason for the game end */
2370 /* [4] is a PGN end game-token, preceded by " " */
2371 /* For start-of-game: */
2372 /* [3] begins with "Creating" or "Continuing" */
2373 /* [4] is " *" or empty (don't care). */
2374 int gamenum = atoi(star_match[0]);
2375 char *whitename, *blackname, *why, *endtoken;
2376 ChessMove endtype = (ChessMove) 0;
2379 whitename = star_match[1];
2380 blackname = star_match[2];
2381 why = star_match[3];
2382 endtoken = star_match[4];
2384 whitename = star_match[1];
2385 blackname = star_match[3];
2386 why = star_match[5];
2387 endtoken = star_match[6];
2390 /* Game start messages */
2391 if (strncmp(why, "Creating ", 9) == 0 ||
2392 strncmp(why, "Continuing ", 11) == 0) {
2393 gs_gamenum = gamenum;
2394 strcpy(gs_kind, strchr(why, ' ') + 1);
2396 if (appData.zippyPlay) {
2397 ZippyGameStart(whitename, blackname);
2403 /* Game end messages */
2404 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2405 ics_gamenum != gamenum) {
2408 while (endtoken[0] == ' ') endtoken++;
2409 switch (endtoken[0]) {
2412 endtype = GameUnfinished;
2415 endtype = BlackWins;
2418 if (endtoken[1] == '/')
2419 endtype = GameIsDrawn;
2421 endtype = WhiteWins;
2424 GameEnds(endtype, why, GE_ICS);
2426 if (appData.zippyPlay && first.initDone) {
2427 ZippyGameEnd(endtype, why);
2428 if (first.pr == NULL) {
2429 /* Start the next process early so that we'll
2430 be ready for the next challenge */
2431 StartChessProgram(&first);
2433 /* Send "new" early, in case this command takes
2434 a long time to finish, so that we'll be ready
2435 for the next challenge. */
2442 if (looking_at(buf, &i, "Removing game * from observation") ||
2443 looking_at(buf, &i, "no longer observing game *") ||
2444 looking_at(buf, &i, "Game * (*) has no examiners")) {
2445 if (gameMode == IcsObserving &&
2446 atoi(star_match[0]) == ics_gamenum)
2451 ics_user_moved = FALSE;
2456 if (looking_at(buf, &i, "no longer examining game *")) {
2457 if (gameMode == IcsExamining &&
2458 atoi(star_match[0]) == ics_gamenum)
2462 ics_user_moved = FALSE;
2467 /* Advance leftover_start past any newlines we find,
2468 so only partial lines can get reparsed */
2469 if (looking_at(buf, &i, "\n")) {
2470 prevColor = curColor;
2471 if (curColor != ColorNormal) {
2472 if (oldi > next_out) {
2473 SendToPlayer(&buf[next_out], oldi - next_out);
2476 Colorize(ColorNormal, FALSE);
2477 curColor = ColorNormal;
2479 if (started == STARTED_BOARD) {
2480 started = STARTED_NONE;
2481 parse[parse_pos] = NULLCHAR;
2482 ParseBoard12(parse);
2485 /* Send premove here */
2486 if (appData.premove) {
2488 if (currentMove == 0 &&
2489 gameMode == IcsPlayingWhite &&
2490 appData.premoveWhite) {
2491 sprintf(str, "%s%s\n", ics_prefix,
2492 appData.premoveWhiteText);
2493 if (appData.debugMode)
2494 fprintf(debugFP, "Sending premove:\n");
2496 } else if (currentMove == 1 &&
2497 gameMode == IcsPlayingBlack &&
2498 appData.premoveBlack) {
2499 sprintf(str, "%s%s\n", ics_prefix,
2500 appData.premoveBlackText);
2501 if (appData.debugMode)
2502 fprintf(debugFP, "Sending premove:\n");
2504 } else if (gotPremove) {
2506 ClearPremoveHighlights();
2507 if (appData.debugMode)
2508 fprintf(debugFP, "Sending premove:\n");
2509 UserMoveEvent(premoveFromX, premoveFromY,
2510 premoveToX, premoveToY,
2515 /* Usually suppress following prompt */
2516 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2517 if (looking_at(buf, &i, "*% ")) {
2518 savingComment = FALSE;
2522 } else if (started == STARTED_HOLDINGS) {
2524 char new_piece[MSG_SIZ];
2525 started = STARTED_NONE;
2526 parse[parse_pos] = NULLCHAR;
2527 if (appData.debugMode)
2528 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2529 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2530 gamenum == ics_gamenum) {
2531 if (gameInfo.variant == VariantNormal) {
2532 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2533 /* Get a move list just to see the header, which
2534 will tell us whether this is really bug or zh */
2535 if (ics_getting_history == H_FALSE) {
2536 ics_getting_history = H_REQUESTED;
2537 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2541 new_piece[0] = NULLCHAR;
2542 sscanf(parse, "game %d white [%s black [%s <- %s",
2543 &gamenum, white_holding, black_holding,
2545 white_holding[strlen(white_holding)-1] = NULLCHAR;
2546 black_holding[strlen(black_holding)-1] = NULLCHAR;
2548 if (appData.zippyPlay && first.initDone) {
2549 ZippyHoldings(white_holding, black_holding,
2553 if (tinyLayout || smallLayout) {
2554 char wh[16], bh[16];
2555 PackHolding(wh, white_holding);
2556 PackHolding(bh, black_holding);
2557 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2558 gameInfo.white, gameInfo.black);
2560 sprintf(str, "%s [%s] vs. %s [%s]",
2561 gameInfo.white, white_holding,
2562 gameInfo.black, black_holding);
2564 DrawPosition(FALSE, NULL);
2567 /* Suppress following prompt */
2568 if (looking_at(buf, &i, "*% ")) {
2569 savingComment = FALSE;
2576 i++; /* skip unparsed character and loop back */
2579 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2580 started != STARTED_HOLDINGS && i > next_out) {
2581 SendToPlayer(&buf[next_out], i - next_out);
2585 leftover_len = buf_len - leftover_start;
2586 /* if buffer ends with something we couldn't parse,
2587 reparse it after appending the next read */
2589 } else if (count == 0) {
2590 RemoveInputSource(isr);
2591 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2593 DisplayFatalError(_("Error reading from ICS"), error, 1);
2598 /* Board style 12 looks like this:
2600 <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
2602 * The "<12> " is stripped before it gets to this routine. The two
2603 * trailing 0's (flip state and clock ticking) are later addition, and
2604 * some chess servers may not have them, or may have only the first.
2605 * Additional trailing fields may be added in the future.
2608 #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"
2610 #define RELATION_OBSERVING_PLAYED 0
2611 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2612 #define RELATION_PLAYING_MYMOVE 1
2613 #define RELATION_PLAYING_NOTMYMOVE -1
2614 #define RELATION_EXAMINING 2
2615 #define RELATION_ISOLATED_BOARD -3
2616 #define RELATION_STARTING_POSITION -4 /* FICS only */
2619 ParseBoard12(string)
2622 GameMode newGameMode;
2623 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
2624 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
2625 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2626 char to_play, board_chars[72];
2627 char move_str[500], str[500], elapsed_time[500];
2628 char black[32], white[32];
2630 int prevMove = currentMove;
2633 int fromX, fromY, toX, toY;
2636 fromX = fromY = toX = toY = -1;
2640 if (appData.debugMode)
2641 fprintf(debugFP, _("Parsing board: %s\n"), string);
2643 move_str[0] = NULLCHAR;
2644 elapsed_time[0] = NULLCHAR;
2645 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2646 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2647 &gamenum, white, black, &relation, &basetime, &increment,
2648 &white_stren, &black_stren, &white_time, &black_time,
2649 &moveNum, str, elapsed_time, move_str, &ics_flip,
2653 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2654 DisplayError(str, 0);
2658 /* Convert the move number to internal form */
2659 moveNum = (moveNum - 1) * 2;
2660 if (to_play == 'B') moveNum++;
2661 if (moveNum >= MAX_MOVES) {
2662 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2668 case RELATION_OBSERVING_PLAYED:
2669 case RELATION_OBSERVING_STATIC:
2670 if (gamenum == -1) {
2671 /* Old ICC buglet */
2672 relation = RELATION_OBSERVING_STATIC;
2674 newGameMode = IcsObserving;
2676 case RELATION_PLAYING_MYMOVE:
2677 case RELATION_PLAYING_NOTMYMOVE:
2679 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2680 IcsPlayingWhite : IcsPlayingBlack;
2682 case RELATION_EXAMINING:
2683 newGameMode = IcsExamining;
2685 case RELATION_ISOLATED_BOARD:
2687 /* Just display this board. If user was doing something else,
2688 we will forget about it until the next board comes. */
2689 newGameMode = IcsIdle;
2691 case RELATION_STARTING_POSITION:
2692 newGameMode = gameMode;
2696 /* Modify behavior for initial board display on move listing
2699 switch (ics_getting_history) {
2703 case H_GOT_REQ_HEADER:
2704 case H_GOT_UNREQ_HEADER:
2705 /* This is the initial position of the current game */
2706 gamenum = ics_gamenum;
2707 moveNum = 0; /* old ICS bug workaround */
2708 if (to_play == 'B') {
2709 startedFromSetupPosition = TRUE;
2710 blackPlaysFirst = TRUE;
2712 if (forwardMostMove == 0) forwardMostMove = 1;
2713 if (backwardMostMove == 0) backwardMostMove = 1;
2714 if (currentMove == 0) currentMove = 1;
2716 newGameMode = gameMode;
2717 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2719 case H_GOT_UNWANTED_HEADER:
2720 /* This is an initial board that we don't want */
2722 case H_GETTING_MOVES:
2723 /* Should not happen */
2724 DisplayError(_("Error gathering move list: extra board"), 0);
2725 ics_getting_history = H_FALSE;
2729 /* Take action if this is the first board of a new game, or of a
2730 different game than is currently being displayed. */
2731 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2732 relation == RELATION_ISOLATED_BOARD) {
2734 /* Forget the old game and get the history (if any) of the new one */
2735 if (gameMode != BeginningOfGame) {
2739 if (appData.autoRaiseBoard) BoardToTop();
2741 if (gamenum == -1) {
2742 newGameMode = IcsIdle;
2743 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2744 appData.getMoveList) {
2745 /* Need to get game history */
2746 ics_getting_history = H_REQUESTED;
2747 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2751 /* Initially flip the board to have black on the bottom if playing
2752 black or if the ICS flip flag is set, but let the user change
2753 it with the Flip View button. */
2754 flipView = appData.autoFlipView ?
2755 (newGameMode == IcsPlayingBlack) || ics_flip :
2758 /* Done with values from previous mode; copy in new ones */
2759 gameMode = newGameMode;
2761 ics_gamenum = gamenum;
2762 if (gamenum == gs_gamenum) {
2763 int klen = strlen(gs_kind);
2764 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2765 sprintf(str, "ICS %s", gs_kind);
2766 gameInfo.event = StrSave(str);
2768 gameInfo.event = StrSave("ICS game");
2770 gameInfo.site = StrSave(appData.icsHost);
2771 gameInfo.date = PGNDate();
2772 gameInfo.round = StrSave("-");
2773 gameInfo.white = StrSave(white);
2774 gameInfo.black = StrSave(black);
2775 timeControl = basetime * 60 * 1000;
2776 timeIncrement = increment * 1000;
2777 movesPerSession = 0;
2778 gameInfo.timeControl = TimeControlTagValue();
2779 gameInfo.variant = StringToVariant(gameInfo.event);
2781 /* Do we have the ratings? */
2782 if (strcmp(player1Name, white) == 0 &&
2783 strcmp(player2Name, black) == 0) {
2784 if (appData.debugMode)
2785 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2786 player1Rating, player2Rating);
2787 gameInfo.whiteRating = player1Rating;
2788 gameInfo.blackRating = player2Rating;
2789 } else if (strcmp(player2Name, white) == 0 &&
2790 strcmp(player1Name, black) == 0) {
2791 if (appData.debugMode)
2792 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2793 player2Rating, player1Rating);
2794 gameInfo.whiteRating = player2Rating;
2795 gameInfo.blackRating = player1Rating;
2797 player1Name[0] = player2Name[0] = NULLCHAR;
2799 /* Silence shouts if requested */
2800 if (appData.quietPlay &&
2801 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2802 SendToICS(ics_prefix);
2803 SendToICS("set shout 0\n");
2807 /* Deal with midgame name changes */
2809 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2810 if (gameInfo.white) free(gameInfo.white);
2811 gameInfo.white = StrSave(white);
2813 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2814 if (gameInfo.black) free(gameInfo.black);
2815 gameInfo.black = StrSave(black);
2819 /* Throw away game result if anything actually changes in examine mode */
2820 if (gameMode == IcsExamining && !newGame) {
2821 gameInfo.result = GameUnfinished;
2822 if (gameInfo.resultDetails != NULL) {
2823 free(gameInfo.resultDetails);
2824 gameInfo.resultDetails = NULL;
2828 /* In pausing && IcsExamining mode, we ignore boards coming
2829 in if they are in a different variation than we are. */
2830 if (pauseExamInvalid) return;
2831 if (pausing && gameMode == IcsExamining) {
2832 if (moveNum <= pauseExamForwardMostMove) {
2833 pauseExamInvalid = TRUE;
2834 forwardMostMove = pauseExamForwardMostMove;
2839 /* Parse the board */
2840 for (k = 0; k < 8; k++)
2841 for (j = 0; j < 8; j++)
2842 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2843 CopyBoard(boards[moveNum], board);
2845 startedFromSetupPosition =
2846 !CompareBoards(board, initialPosition);
2849 if (ics_getting_history == H_GOT_REQ_HEADER ||
2850 ics_getting_history == H_GOT_UNREQ_HEADER) {
2851 /* This was an initial position from a move list, not
2852 the current position */
2856 /* Update currentMove and known move number limits */
2857 newMove = newGame || moveNum > forwardMostMove;
2859 /* If we found takebacks during icsEngineAnalyze
2860 try send to engine */
2861 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
2862 takeback = forwardMostMove - moveNum;
2863 for (i = 0; i < takeback; i++) {
2864 if (appData.debugMode) fprintf(debugFP, "take back move\n");
2865 SendToProgram("undo\n", &first);
2869 forwardMostMove = backwardMostMove = currentMove = moveNum;
2870 if (gameMode == IcsExamining && moveNum == 0) {
2871 /* Workaround for ICS limitation: we are not told the wild
2872 type when starting to examine a game. But if we ask for
2873 the move list, the move list header will tell us */
2874 ics_getting_history = H_REQUESTED;
2875 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2878 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2879 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2880 forwardMostMove = moveNum;
2881 if (!pausing || currentMove > forwardMostMove)
2882 currentMove = forwardMostMove;
2884 /* New part of history that is not contiguous with old part */
2885 if (pausing && gameMode == IcsExamining) {
2886 pauseExamInvalid = TRUE;
2887 forwardMostMove = pauseExamForwardMostMove;
2890 forwardMostMove = backwardMostMove = currentMove = moveNum;
2891 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2892 ics_getting_history = H_REQUESTED;
2893 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2898 /* Update the clocks */
2899 if (strchr(elapsed_time, '.')) {
2901 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2902 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2904 /* Time is in seconds */
2905 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2906 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2911 if (appData.zippyPlay && newGame &&
2912 gameMode != IcsObserving && gameMode != IcsIdle &&
2913 gameMode != IcsExamining)
2914 ZippyFirstBoard(moveNum, basetime, increment);
2917 /* Put the move on the move list, first converting
2918 to canonical algebraic form. */
2920 if (moveNum <= backwardMostMove) {
2921 /* We don't know what the board looked like before
2923 strcpy(parseList[moveNum - 1], move_str);
2924 strcat(parseList[moveNum - 1], " ");
2925 strcat(parseList[moveNum - 1], elapsed_time);
2926 moveList[moveNum - 1][0] = NULLCHAR;
2927 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2928 &fromX, &fromY, &toX, &toY, &promoChar)) {
2929 (void) CoordsToAlgebraic(boards[moveNum - 1],
2930 PosFlags(moveNum - 1), EP_UNKNOWN,
2931 fromY, fromX, toY, toX, promoChar,
2932 parseList[moveNum-1]);
2933 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2939 strcat(parseList[moveNum - 1], "+");
2942 strcat(parseList[moveNum - 1], "#");
2945 strcat(parseList[moveNum - 1], " ");
2946 strcat(parseList[moveNum - 1], elapsed_time);
2947 /* currentMoveString is set as a side-effect of ParseOneMove */
2948 strcpy(moveList[moveNum - 1], currentMoveString);
2949 strcat(moveList[moveNum - 1], "\n");
2950 } else if (strcmp(move_str, "none") == 0) {
2951 /* Again, we don't know what the board looked like;
2952 this is really the start of the game. */
2953 parseList[moveNum - 1][0] = NULLCHAR;
2954 moveList[moveNum - 1][0] = NULLCHAR;
2955 backwardMostMove = moveNum;
2956 startedFromSetupPosition = TRUE;
2957 fromX = fromY = toX = toY = -1;
2959 /* Move from ICS was illegal!? Punt. */
2961 if (appData.testLegality && appData.debugMode) {
2962 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2963 DisplayError(str, 0);
2966 strcpy(parseList[moveNum - 1], move_str);
2967 strcat(parseList[moveNum - 1], " ");
2968 strcat(parseList[moveNum - 1], elapsed_time);
2969 moveList[moveNum - 1][0] = NULLCHAR;
2970 fromX = fromY = toX = toY = -1;
2974 /* Send move to chess program (BEFORE animating it). */
2975 if (appData.zippyPlay && !newGame && newMove &&
2976 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2978 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2979 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2980 if (moveList[moveNum - 1][0] == NULLCHAR) {
2981 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2983 DisplayError(str, 0);
2985 if (first.sendTime) {
2986 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2988 SendMoveToProgram(moveNum - 1, &first);
2991 if (first.useColors) {
2992 SendToProgram(gameMode == IcsPlayingWhite ?
2994 "black\ngo\n", &first);
2996 SendToProgram("go\n", &first);
2998 first.maybeThinking = TRUE;
3001 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3002 if (moveList[moveNum - 1][0] == NULLCHAR) {
3003 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3004 DisplayError(str, 0);
3006 SendMoveToProgram(moveNum - 1, &first);
3013 if (moveNum > 0 && !gotPremove) {
3014 /* If move comes from a remote source, animate it. If it
3015 isn't remote, it will have already been animated. */
3016 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3017 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3019 if (!pausing && appData.highlightLastMove) {
3020 SetHighlights(fromX, fromY, toX, toY);
3024 /* Start the clocks */
3025 whiteFlag = blackFlag = FALSE;
3026 appData.clockMode = !(basetime == 0 && increment == 0);
3028 ics_clock_paused = TRUE;
3030 } else if (ticking == 1) {
3031 ics_clock_paused = FALSE;
3033 if (gameMode == IcsIdle ||
3034 relation == RELATION_OBSERVING_STATIC ||
3035 relation == RELATION_EXAMINING ||
3037 DisplayBothClocks();
3041 /* Display opponents and material strengths */
3042 if (gameInfo.variant != VariantBughouse &&
3043 gameInfo.variant != VariantCrazyhouse) {
3044 if (tinyLayout || smallLayout) {
3045 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3046 gameInfo.white, white_stren, gameInfo.black, black_stren,
3047 basetime, increment);
3049 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3050 gameInfo.white, white_stren, gameInfo.black, black_stren,
3051 basetime, increment);
3057 /* Display the board */
3060 if (appData.premove)
3062 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3063 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3064 ClearPremoveHighlights();
3066 DrawPosition(FALSE, boards[currentMove]);
3067 DisplayMove(moveNum - 1);
3068 if (appData.ringBellAfterMoves && !ics_user_moved)
3072 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3079 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3080 ics_getting_history = H_REQUESTED;
3081 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3087 AnalysisPeriodicEvent(force)
3090 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3091 && !force) || !appData.periodicUpdates)
3094 /* Send . command to Crafty to collect stats */
3095 SendToProgram(".\n", &first);
3097 /* Don't send another until we get a response (this makes
3098 us stop sending to old Crafty's which don't understand
3099 the "." command (sending illegal cmds resets node count & time,
3100 which looks bad)) */
3101 programStats.ok_to_send = 0;
3105 SendMoveToProgram(moveNum, cps)
3107 ChessProgramState *cps;
3110 if (cps->useUsermove) {
3111 SendToProgram("usermove ", cps);
3115 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3116 int len = space - parseList[moveNum];
3117 memcpy(buf, parseList[moveNum], len);
3119 buf[len] = NULLCHAR;
3121 sprintf(buf, "%s\n", parseList[moveNum]);
3123 SendToProgram(buf, cps);
3125 SendToProgram(moveList[moveNum], cps);
3130 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3132 int fromX, fromY, toX, toY;
3134 char user_move[MSG_SIZ];
3138 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3139 (int)moveType, fromX, fromY, toX, toY);
3140 DisplayError(user_move + strlen("say "), 0);
3142 case WhiteKingSideCastle:
3143 case BlackKingSideCastle:
3144 case WhiteQueenSideCastleWild:
3145 case BlackQueenSideCastleWild:
3146 sprintf(user_move, "o-o\n");
3148 case WhiteQueenSideCastle:
3149 case BlackQueenSideCastle:
3150 case WhiteKingSideCastleWild:
3151 case BlackKingSideCastleWild:
3152 sprintf(user_move, "o-o-o\n");
3154 case WhitePromotionQueen:
3155 case BlackPromotionQueen:
3156 case WhitePromotionRook:
3157 case BlackPromotionRook:
3158 case WhitePromotionBishop:
3159 case BlackPromotionBishop:
3160 case WhitePromotionKnight:
3161 case BlackPromotionKnight:
3162 case WhitePromotionKing:
3163 case BlackPromotionKing:
3164 sprintf(user_move, "%c%c%c%c=%c\n",
3165 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3166 PieceToChar(PromoPiece(moveType)));
3170 sprintf(user_move, "%c@%c%c\n",
3171 ToUpper(PieceToChar((ChessSquare) fromX)),
3172 'a' + toX, '1' + toY);
3175 case WhiteCapturesEnPassant:
3176 case BlackCapturesEnPassant:
3177 case IllegalMove: /* could be a variant we don't quite understand */
3178 sprintf(user_move, "%c%c%c%c\n",
3179 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3182 SendToICS(user_move);
3186 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3191 if (rf == DROP_RANK) {
3192 sprintf(move, "%c@%c%c\n",
3193 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3195 if (promoChar == 'x' || promoChar == NULLCHAR) {
3196 sprintf(move, "%c%c%c%c\n",
3197 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3199 sprintf(move, "%c%c%c%c%c\n",
3200 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3206 ProcessICSInitScript(f)
3211 while (fgets(buf, MSG_SIZ, f)) {
3212 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3219 /* Parser for moves from gnuchess, ICS, or user typein box */
3221 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3224 ChessMove *moveType;
3225 int *fromX, *fromY, *toX, *toY;
3228 *moveType = yylexstr(moveNum, move);
3229 switch (*moveType) {
3230 case WhitePromotionQueen:
3231 case BlackPromotionQueen:
3232 case WhitePromotionRook:
3233 case BlackPromotionRook:
3234 case WhitePromotionBishop:
3235 case BlackPromotionBishop:
3236 case WhitePromotionKnight:
3237 case BlackPromotionKnight:
3238 case WhitePromotionKing:
3239 case BlackPromotionKing:
3241 case WhiteCapturesEnPassant:
3242 case BlackCapturesEnPassant:
3243 case WhiteKingSideCastle:
3244 case WhiteQueenSideCastle:
3245 case BlackKingSideCastle:
3246 case BlackQueenSideCastle:
3247 case WhiteKingSideCastleWild:
3248 case WhiteQueenSideCastleWild:
3249 case BlackKingSideCastleWild:
3250 case BlackQueenSideCastleWild:
3251 case IllegalMove: /* bug or odd chess variant */
3252 *fromX = currentMoveString[0] - 'a';
3253 *fromY = currentMoveString[1] - '1';
3254 *toX = currentMoveString[2] - 'a';
3255 *toY = currentMoveString[3] - '1';
3256 *promoChar = currentMoveString[4];
3257 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3258 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3259 *fromX = *fromY = *toX = *toY = 0;
3262 if (appData.testLegality) {
3263 return (*moveType != IllegalMove);
3265 return !(fromX == fromY && toX == toY);
3270 *fromX = *moveType == WhiteDrop ?
3271 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3272 (int) CharToPiece(ToLower(currentMoveString[0]));
3274 *toX = currentMoveString[2] - 'a';
3275 *toY = currentMoveString[3] - '1';
3276 *promoChar = NULLCHAR;
3280 case ImpossibleMove:
3281 case (ChessMove) 0: /* end of file */
3291 *fromX = *fromY = *toX = *toY = 0;
3292 *promoChar = NULLCHAR;
3299 InitPosition(redraw)
3302 currentMove = forwardMostMove = backwardMostMove = 0;
3303 switch (gameInfo.variant) {
3305 CopyBoard(boards[0], initialPosition);
3307 case VariantTwoKings:
3308 CopyBoard(boards[0], twoKingsPosition);
3309 startedFromSetupPosition = TRUE;
3311 case VariantWildCastle:
3312 CopyBoard(boards[0], initialPosition);
3313 /* !!?shuffle with kings guaranteed to be on d or e file */
3315 case VariantNoCastle:
3316 CopyBoard(boards[0], initialPosition);
3317 /* !!?unconstrained back-rank shuffle */
3319 case VariantFischeRandom:
3320 CopyBoard(boards[0], initialPosition);
3321 /* !!shuffle according to FR rules */
3325 DrawPosition(FALSE, boards[currentMove]);
3329 SendBoard(cps, moveNum)
3330 ChessProgramState *cps;
3333 char message[MSG_SIZ];
3335 if (cps->useSetboard) {
3336 char* fen = PositionToFEN(moveNum);
3337 sprintf(message, "setboard %s\n", fen);
3338 SendToProgram(message, cps);