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;
1495 if (appData.debugMode) {
1497 fprintf(debugFP, "<ICS: ");
1498 show_bytes(debugFP, data, count);
1499 fprintf(debugFP, "\n");
1504 /* If last read ended with a partial line that we couldn't parse,
1505 prepend it to the new read and try again. */
1506 if (leftover_len > 0) {
1507 for (i=0; i<leftover_len; i++)
1508 buf[i] = buf[leftover_start + i];
1511 /* Copy in new characters, removing nulls and \r's */
1512 buf_len = leftover_len;
1513 for (i = 0; i < count; i++) {
1514 if (data[i] != NULLCHAR && data[i] != '\r')
1515 buf[buf_len++] = data[i];
1518 buf[buf_len] = NULLCHAR;
1519 next_out = leftover_len;
1523 while (i < buf_len) {
1524 /* Deal with part of the TELNET option negotiation
1525 protocol. We refuse to do anything beyond the
1526 defaults, except that we allow the WILL ECHO option,
1527 which ICS uses to turn off password echoing when we are
1528 directly connected to it. We reject this option
1529 if localLineEditing mode is on (always on in xboard)
1530 and we are talking to port 23, which might be a real
1531 telnet server that will try to keep WILL ECHO on permanently.
1533 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1534 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1535 unsigned char option;
1537 switch ((unsigned char) buf[++i]) {
1539 if (appData.debugMode)
1540 fprintf(debugFP, "\n<WILL ");
1541 switch (option = (unsigned char) buf[++i]) {
1543 if (appData.debugMode)
1544 fprintf(debugFP, "ECHO ");
1545 /* Reply only if this is a change, according
1546 to the protocol rules. */
1547 if (remoteEchoOption) break;
1548 if (appData.localLineEditing &&
1549 atoi(appData.icsPort) == TN_PORT) {
1550 TelnetRequest(TN_DONT, TN_ECHO);
1553 TelnetRequest(TN_DO, TN_ECHO);
1554 remoteEchoOption = TRUE;
1558 if (appData.debugMode)
1559 fprintf(debugFP, "%d ", option);
1560 /* Whatever this is, we don't want it. */
1561 TelnetRequest(TN_DONT, option);
1566 if (appData.debugMode)
1567 fprintf(debugFP, "\n<WONT ");
1568 switch (option = (unsigned char) buf[++i]) {
1570 if (appData.debugMode)
1571 fprintf(debugFP, "ECHO ");
1572 /* Reply only if this is a change, according
1573 to the protocol rules. */
1574 if (!remoteEchoOption) break;
1576 TelnetRequest(TN_DONT, TN_ECHO);
1577 remoteEchoOption = FALSE;
1580 if (appData.debugMode)
1581 fprintf(debugFP, "%d ", (unsigned char) option);
1582 /* Whatever this is, it must already be turned
1583 off, because we never agree to turn on
1584 anything non-default, so according to the
1585 protocol rules, we don't reply. */
1590 if (appData.debugMode)
1591 fprintf(debugFP, "\n<DO ");
1592 switch (option = (unsigned char) buf[++i]) {
1594 /* Whatever this is, we refuse to do it. */
1595 if (appData.debugMode)
1596 fprintf(debugFP, "%d ", option);
1597 TelnetRequest(TN_WONT, option);
1602 if (appData.debugMode)
1603 fprintf(debugFP, "\n<DONT ");
1604 switch (option = (unsigned char) buf[++i]) {
1606 if (appData.debugMode)
1607 fprintf(debugFP, "%d ", option);
1608 /* Whatever this is, we are already not doing
1609 it, because we never agree to do anything
1610 non-default, so according to the protocol
1611 rules, we don't reply. */
1616 if (appData.debugMode)
1617 fprintf(debugFP, "\n<IAC ");
1618 /* Doubled IAC; pass it through */
1622 if (appData.debugMode)
1623 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1624 /* Drop all other telnet commands on the floor */
1627 if (oldi > next_out)
1628 SendToPlayer(&buf[next_out], oldi - next_out);
1634 /* OK, this at least will *usually* work */
1635 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1639 if (loggedOn && !intfSet) {
1640 if (ics_type == ICS_ICC) {
1642 "/set-quietly interface %s\n/set-quietly style 12\n",
1645 } else if (ics_type == ICS_CHESSNET) {
1646 sprintf(str, "/style 12\n");
1648 strcpy(str, "alias $ @\n$set interface ");
1649 strcat(str, programVersion);
1650 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1652 strcat(str, "$iset nohighlight 1\n");
1654 strcat(str, "$iset lock 1\n$style 12\n");
1660 if (started == STARTED_COMMENT) {
1661 /* Accumulate characters in comment */
1662 parse[parse_pos++] = buf[i];
1663 if (buf[i] == '\n') {
1664 parse[parse_pos] = NULLCHAR;
1665 AppendComment(forwardMostMove, StripHighlight(parse));
1666 started = STARTED_NONE;
1668 /* Don't match patterns against characters in chatter */
1673 if (started == STARTED_CHATTER) {
1674 if (buf[i] != '\n') {
1675 /* Don't match patterns against characters in chatter */
1679 started = STARTED_NONE;
1682 /* Kludge to deal with rcmd protocol */
1683 if (firstTime && looking_at(buf, &i, "\001*")) {
1684 DisplayFatalError(&buf[1], 0, 1);
1690 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1693 if (appData.debugMode)
1694 fprintf(debugFP, "ics_type %d\n", ics_type);
1697 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1698 ics_type = ICS_FICS;
1700 if (appData.debugMode)
1701 fprintf(debugFP, "ics_type %d\n", ics_type);
1704 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1705 ics_type = ICS_CHESSNET;
1707 if (appData.debugMode)
1708 fprintf(debugFP, "ics_type %d\n", ics_type);
1713 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1714 looking_at(buf, &i, "Logging you in as \"*\"") ||
1715 looking_at(buf, &i, "will be \"*\""))) {
1716 strcpy(ics_handle, star_match[0]);
1720 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1722 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1723 DisplayIcsInteractionTitle(buf);
1724 have_set_title = TRUE;
1727 /* skip finger notes */
1728 if (started == STARTED_NONE &&
1729 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1730 (buf[i] == '1' && buf[i+1] == '0')) &&
1731 buf[i+2] == ':' && buf[i+3] == ' ') {
1732 started = STARTED_CHATTER;
1737 /* skip formula vars */
1738 if (started == STARTED_NONE &&
1739 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1740 started = STARTED_CHATTER;
1746 if (appData.zippyTalk || appData.zippyPlay) {
1747 /* Backup address for color zippy lines */
1751 if (loggedOn == TRUE)
1752 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
1753 (appData.zippyPlay && ZippyMatch(buf, &backup)));
1755 if (ZippyControl(buf, &i) ||
1756 ZippyConverse(buf, &i) ||
1757 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1759 if (!appData.colorize) continue;
1764 if (/* Don't color "message" or "messages" output */
1765 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1766 looking_at(buf, &i, "*. * at *:*: ") ||
1767 looking_at(buf, &i, "--* (*:*): ") ||
1768 /* Regular tells and says */
1769 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1770 looking_at(buf, &i, "* (your partner) tells you: ") ||
1771 looking_at(buf, &i, "* says: ") ||
1772 /* Message notifications (same color as tells) */
1773 looking_at(buf, &i, "* has left a message ") ||
1774 looking_at(buf, &i, "* just sent you a message:\n") ||
1775 /* Whispers and kibitzes */
1776 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1777 looking_at(buf, &i, "* kibitzes: ") ||
1779 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1781 if (tkind == 1 && strchr(star_match[0], ':')) {
1782 /* Avoid "tells you:" spoofs in channels */
1785 if (star_match[0][0] == NULLCHAR ||
1786 strchr(star_match[0], ' ') ||
1787 (tkind == 3 && strchr(star_match[1], ' '))) {
1788 /* Reject bogus matches */
1791 if (appData.colorize) {
1792 if (oldi > next_out) {
1793 SendToPlayer(&buf[next_out], oldi - next_out);
1798 Colorize(ColorTell, FALSE);
1799 curColor = ColorTell;
1802 Colorize(ColorKibitz, FALSE);
1803 curColor = ColorKibitz;
1806 p = strrchr(star_match[1], '(');
1813 Colorize(ColorChannel1, FALSE);
1814 curColor = ColorChannel1;
1816 Colorize(ColorChannel, FALSE);
1817 curColor = ColorChannel;
1821 curColor = ColorNormal;
1825 if (started == STARTED_NONE && appData.autoComment &&
1826 (gameMode == IcsObserving ||
1827 gameMode == IcsPlayingWhite ||
1828 gameMode == IcsPlayingBlack)) {
1829 parse_pos = i - oldi;
1830 memcpy(parse, &buf[oldi], parse_pos);
1831 parse[parse_pos] = NULLCHAR;
1832 started = STARTED_COMMENT;
1833 savingComment = TRUE;
1835 started = STARTED_CHATTER;
1836 savingComment = FALSE;
1843 if (looking_at(buf, &i, "* s-shouts: ") ||
1844 looking_at(buf, &i, "* c-shouts: ")) {
1845 if (appData.colorize) {
1846 if (oldi > next_out) {
1847 SendToPlayer(&buf[next_out], oldi - next_out);
1850 Colorize(ColorSShout, FALSE);
1851 curColor = ColorSShout;
1854 started = STARTED_CHATTER;
1858 if (looking_at(buf, &i, "--->")) {
1863 if (looking_at(buf, &i, "* shouts: ") ||
1864 looking_at(buf, &i, "--> ")) {
1865 if (appData.colorize) {
1866 if (oldi > next_out) {
1867 SendToPlayer(&buf[next_out], oldi - next_out);
1870 Colorize(ColorShout, FALSE);
1871 curColor = ColorShout;
1874 started = STARTED_CHATTER;
1878 if (looking_at( buf, &i, "Challenge:")) {
1879 if (appData.colorize) {
1880 if (oldi > next_out) {
1881 SendToPlayer(&buf[next_out], oldi - next_out);
1884 Colorize(ColorChallenge, FALSE);
1885 curColor = ColorChallenge;
1891 if (looking_at(buf, &i, "* offers you") ||
1892 looking_at(buf, &i, "* offers to be") ||
1893 looking_at(buf, &i, "* would like to") ||
1894 looking_at(buf, &i, "* requests to") ||
1895 looking_at(buf, &i, "Your opponent offers") ||
1896 looking_at(buf, &i, "Your opponent requests")) {
1898 if (appData.colorize) {
1899 if (oldi > next_out) {
1900 SendToPlayer(&buf[next_out], oldi - next_out);
1903 Colorize(ColorRequest, FALSE);
1904 curColor = ColorRequest;
1909 if (looking_at(buf, &i, "* (*) seeking")) {
1910 if (appData.colorize) {
1911 if (oldi > next_out) {
1912 SendToPlayer(&buf[next_out], oldi - next_out);
1915 Colorize(ColorSeek, FALSE);
1916 curColor = ColorSeek;
1921 if (looking_at(buf, &i, "\\ ")) {
1922 if (prevColor != ColorNormal) {
1923 if (oldi > next_out) {
1924 SendToPlayer(&buf[next_out], oldi - next_out);
1927 Colorize(prevColor, TRUE);
1928 curColor = prevColor;
1930 if (savingComment) {
1931 parse_pos = i - oldi;
1932 memcpy(parse, &buf[oldi], parse_pos);
1933 parse[parse_pos] = NULLCHAR;
1934 started = STARTED_COMMENT;
1936 started = STARTED_CHATTER;
1941 if (looking_at(buf, &i, "Black Strength :") ||
1942 looking_at(buf, &i, "<<< style 10 board >>>") ||
1943 looking_at(buf, &i, "<10>") ||
1944 looking_at(buf, &i, "#@#")) {
1945 /* Wrong board style */
1947 SendToICS(ics_prefix);
1948 SendToICS("set style 12\n");
1949 SendToICS(ics_prefix);
1950 SendToICS("refresh\n");
1954 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1956 have_sent_ICS_logon = 1;
1960 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1961 (looking_at(buf, &i, "\n<12> ") ||
1962 looking_at(buf, &i, "<12> "))) {
1964 if (oldi > next_out) {
1965 SendToPlayer(&buf[next_out], oldi - next_out);
1968 started = STARTED_BOARD;
1973 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1974 looking_at(buf, &i, "<b1> ")) {
1975 if (oldi > next_out) {
1976 SendToPlayer(&buf[next_out], oldi - next_out);
1979 started = STARTED_HOLDINGS;
1984 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1986 /* Header for a move list -- first line */
1988 switch (ics_getting_history) {
1992 case BeginningOfGame:
1993 /* User typed "moves" or "oldmoves" while we
1994 were idle. Pretend we asked for these
1995 moves and soak them up so user can step
1996 through them and/or save them.
1999 gameMode = IcsObserving;
2002 ics_getting_history = H_GOT_UNREQ_HEADER;
2004 case EditGame: /*?*/
2005 case EditPosition: /*?*/
2006 /* Should above feature work in these modes too? */
2007 /* For now it doesn't */
2008 ics_getting_history = H_GOT_UNWANTED_HEADER;
2011 ics_getting_history = H_GOT_UNWANTED_HEADER;
2016 /* Is this the right one? */
2017 if (gameInfo.white && gameInfo.black &&
2018 strcmp(gameInfo.white, star_match[0]) == 0 &&
2019 strcmp(gameInfo.black, star_match[2]) == 0) {
2021 ics_getting_history = H_GOT_REQ_HEADER;
2024 case H_GOT_REQ_HEADER:
2025 case H_GOT_UNREQ_HEADER:
2026 case H_GOT_UNWANTED_HEADER:
2027 case H_GETTING_MOVES:
2028 /* Should not happen */
2029 DisplayError(_("Error gathering move list: two headers"), 0);
2030 ics_getting_history = H_FALSE;
2034 /* Save player ratings into gameInfo if needed */
2035 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2036 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2037 (gameInfo.whiteRating == -1 ||
2038 gameInfo.blackRating == -1)) {
2040 gameInfo.whiteRating = string_to_rating(star_match[1]);
2041 gameInfo.blackRating = string_to_rating(star_match[3]);
2042 if (appData.debugMode)
2043 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2044 gameInfo.whiteRating, gameInfo.blackRating);
2049 if (looking_at(buf, &i,
2050 "* * match, initial time: * minute*, increment: * second")) {
2051 /* Header for a move list -- second line */
2052 /* Initial board will follow if this is a wild game */
2054 if (gameInfo.event != NULL) free(gameInfo.event);
2055 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2056 gameInfo.event = StrSave(str);
2057 gameInfo.variant = StringToVariant(gameInfo.event);
2061 if (looking_at(buf, &i, "Move ")) {
2062 /* Beginning of a move list */
2063 switch (ics_getting_history) {
2065 /* Normally should not happen */
2066 /* Maybe user hit reset while we were parsing */
2069 /* Happens if we are ignoring a move list that is not
2070 * the one we just requested. Common if the user
2071 * tries to observe two games without turning off
2074 case H_GETTING_MOVES:
2075 /* Should not happen */
2076 DisplayError(_("Error gathering move list: nested"), 0);
2077 ics_getting_history = H_FALSE;
2079 case H_GOT_REQ_HEADER:
2080 ics_getting_history = H_GETTING_MOVES;
2081 started = STARTED_MOVES;
2083 if (oldi > next_out) {
2084 SendToPlayer(&buf[next_out], oldi - next_out);
2087 case H_GOT_UNREQ_HEADER:
2088 ics_getting_history = H_GETTING_MOVES;
2089 started = STARTED_MOVES_NOHIDE;
2092 case H_GOT_UNWANTED_HEADER:
2093 ics_getting_history = H_FALSE;
2099 if (looking_at(buf, &i, "% ") ||
2100 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2101 && looking_at(buf, &i, "}*"))) {
2102 savingComment = FALSE;
2105 case STARTED_MOVES_NOHIDE:
2106 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2107 parse[parse_pos + i - oldi] = NULLCHAR;
2108 ParseGameHistory(parse);
2110 if (appData.zippyPlay && first.initDone) {
2111 FeedMovesToProgram(&first, forwardMostMove);
2112 if (gameMode == IcsPlayingWhite) {
2113 if (WhiteOnMove(forwardMostMove)) {
2114 if (first.sendTime) {
2115 if (first.useColors) {
2116 SendToProgram("black\n", &first);
2118 SendTimeRemaining(&first, TRUE);
2120 if (first.useColors) {
2121 SendToProgram("white\ngo\n", &first);
2123 SendToProgram("go\n", &first);
2125 first.maybeThinking = TRUE;
2127 if (first.usePlayother) {
2128 if (first.sendTime) {
2129 SendTimeRemaining(&first, TRUE);
2131 SendToProgram("playother\n", &first);
2137 } else if (gameMode == IcsPlayingBlack) {
2138 if (!WhiteOnMove(forwardMostMove)) {
2139 if (first.sendTime) {
2140 if (first.useColors) {
2141 SendToProgram("white\n", &first);
2143 SendTimeRemaining(&first, FALSE);
2145 if (first.useColors) {
2146 SendToProgram("black\ngo\n", &first);
2148 SendToProgram("go\n", &first);
2150 first.maybeThinking = TRUE;
2152 if (first.usePlayother) {
2153 if (first.sendTime) {
2154 SendTimeRemaining(&first, FALSE);
2156 SendToProgram("playother\n", &first);
2165 if (gameMode == IcsObserving && ics_gamenum == -1) {
2166 /* Moves came from oldmoves or moves command
2167 while we weren't doing anything else.
2169 currentMove = forwardMostMove;
2170 ClearHighlights();/*!!could figure this out*/
2171 flipView = appData.flipView;
2172 DrawPosition(FALSE, boards[currentMove]);
2173 DisplayBothClocks();
2174 sprintf(str, "%s vs. %s",
2175 gameInfo.white, gameInfo.black);
2179 /* Moves were history of an active game */
2180 if (gameInfo.resultDetails != NULL) {
2181 free(gameInfo.resultDetails);
2182 gameInfo.resultDetails = NULL;
2185 HistorySet(parseList, backwardMostMove,
2186 forwardMostMove, currentMove-1);
2187 DisplayMove(currentMove - 1);
2188 if (started == STARTED_MOVES) next_out = i;
2189 started = STARTED_NONE;
2190 ics_getting_history = H_FALSE;
2193 case STARTED_OBSERVE:
2194 started = STARTED_NONE;
2195 SendToICS(ics_prefix);
2196 SendToICS("refresh\n");
2205 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2206 started == STARTED_HOLDINGS ||
2207 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2208 /* Accumulate characters in move list or board */
2209 parse[parse_pos++] = buf[i];
2212 /* Start of game messages. Mostly we detect start of game
2213 when the first board image arrives. On some versions
2214 of the ICS, though, we need to do a "refresh" after starting
2215 to observe in order to get the current board right away. */
2216 if (looking_at(buf, &i, "Adding game * to observation list")) {
2217 started = STARTED_OBSERVE;
2221 /* Handle auto-observe */
2222 if (appData.autoObserve &&
2223 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2224 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2226 /* Choose the player that was highlighted, if any. */
2227 if (star_match[0][0] == '\033' ||
2228 star_match[1][0] != '\033') {
2229 player = star_match[0];
2231 player = star_match[2];
2233 sprintf(str, "%sobserve %s\n",
2234 ics_prefix, StripHighlightAndTitle(player));
2237 /* Save ratings from notify string */
2238 strcpy(player1Name, star_match[0]);
2239 player1Rating = string_to_rating(star_match[1]);
2240 strcpy(player2Name, star_match[2]);
2241 player2Rating = string_to_rating(star_match[3]);
2243 if (appData.debugMode)
2245 "Ratings from 'Game notification:' %s %d, %s %d\n",
2246 player1Name, player1Rating,
2247 player2Name, player2Rating);
2252 /* Deal with automatic examine mode after a game,
2253 and with IcsObserving -> IcsExamining transition */
2254 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2255 looking_at(buf, &i, "has made you an examiner of game *")) {
2257 int gamenum = atoi(star_match[0]);
2258 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2259 gamenum == ics_gamenum) {
2260 /* We were already playing or observing this game;
2261 no need to refetch history */
2262 gameMode = IcsExamining;
2264 pauseExamForwardMostMove = forwardMostMove;
2265 } else if (currentMove < forwardMostMove) {
2266 ForwardInner(forwardMostMove);
2269 /* I don't think this case really can happen */
2270 SendToICS(ics_prefix);
2271 SendToICS("refresh\n");
2276 /* Error messages */
2277 if (ics_user_moved) {
2278 if (looking_at(buf, &i, "Illegal move") ||
2279 looking_at(buf, &i, "Not a legal move") ||
2280 looking_at(buf, &i, "Your king is in check") ||
2281 looking_at(buf, &i, "It isn't your turn") ||
2282 looking_at(buf, &i, "It is not your move")) {
2285 if (forwardMostMove > backwardMostMove) {
2286 currentMove = --forwardMostMove;
2287 DisplayMove(currentMove - 1); /* before DMError */
2288 DisplayMoveError("Illegal move (rejected by ICS)");
2289 DrawPosition(FALSE, boards[currentMove]);
2291 DisplayBothClocks();
2297 if (looking_at(buf, &i, "still have time") ||
2298 looking_at(buf, &i, "not out of time") ||
2299 looking_at(buf, &i, "either player is out of time") ||
2300 looking_at(buf, &i, "has timeseal; checking")) {
2301 /* We must have called his flag a little too soon */
2302 whiteFlag = blackFlag = FALSE;
2306 if (looking_at(buf, &i, "added * seconds to") ||
2307 looking_at(buf, &i, "seconds were added to")) {
2308 /* Update the clocks */
2309 SendToICS(ics_prefix);
2310 SendToICS("refresh\n");
2314 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2315 ics_clock_paused = TRUE;
2320 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2321 ics_clock_paused = FALSE;
2326 /* Grab player ratings from the Creating: message.
2327 Note we have to check for the special case when
2328 the ICS inserts things like [white] or [black]. */
2329 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2330 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2332 0 player 1 name (not necessarily white)
2334 2 empty, white, or black (IGNORED)
2335 3 player 2 name (not necessarily black)
2338 The names/ratings are sorted out when the game
2339 actually starts (below).
2341 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2342 player1Rating = string_to_rating(star_match[1]);
2343 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2344 player2Rating = string_to_rating(star_match[4]);
2346 if (appData.debugMode)
2348 "Ratings from 'Creating:' %s %d, %s %d\n",
2349 player1Name, player1Rating,
2350 player2Name, player2Rating);
2355 /* Improved generic start/end-of-game messages */
2356 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2357 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2358 /* If tkind == 0: */
2359 /* star_match[0] is the game number */
2360 /* [1] is the white player's name */
2361 /* [2] is the black player's name */
2362 /* For end-of-game: */
2363 /* [3] is the reason for the game end */
2364 /* [4] is a PGN end game-token, preceded by " " */
2365 /* For start-of-game: */
2366 /* [3] begins with "Creating" or "Continuing" */
2367 /* [4] is " *" or empty (don't care). */
2368 int gamenum = atoi(star_match[0]);
2369 char *whitename, *blackname, *why, *endtoken;
2370 ChessMove endtype = (ChessMove) 0;
2373 whitename = star_match[1];
2374 blackname = star_match[2];
2375 why = star_match[3];
2376 endtoken = star_match[4];
2378 whitename = star_match[1];
2379 blackname = star_match[3];
2380 why = star_match[5];
2381 endtoken = star_match[6];
2384 /* Game start messages */
2385 if (strncmp(why, "Creating ", 9) == 0 ||
2386 strncmp(why, "Continuing ", 11) == 0) {
2387 gs_gamenum = gamenum;
2388 strcpy(gs_kind, strchr(why, ' ') + 1);
2390 if (appData.zippyPlay) {
2391 ZippyGameStart(whitename, blackname);
2397 /* Game end messages */
2398 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2399 ics_gamenum != gamenum) {
2402 while (endtoken[0] == ' ') endtoken++;
2403 switch (endtoken[0]) {
2406 endtype = GameUnfinished;
2409 endtype = BlackWins;
2412 if (endtoken[1] == '/')
2413 endtype = GameIsDrawn;
2415 endtype = WhiteWins;
2418 GameEnds(endtype, why, GE_ICS);
2420 if (appData.zippyPlay && first.initDone) {
2421 ZippyGameEnd(endtype, why);
2422 if (first.pr == NULL) {
2423 /* Start the next process early so that we'll
2424 be ready for the next challenge */
2425 StartChessProgram(&first);
2427 /* Send "new" early, in case this command takes
2428 a long time to finish, so that we'll be ready
2429 for the next challenge. */
2436 if (looking_at(buf, &i, "Removing game * from observation") ||
2437 looking_at(buf, &i, "no longer observing game *") ||
2438 looking_at(buf, &i, "Game * (*) has no examiners")) {
2439 if (gameMode == IcsObserving &&
2440 atoi(star_match[0]) == ics_gamenum)
2442 /* icsEngineAnalyze */
2443 if (appData.icsEngineAnalyze) {
2450 ics_user_moved = FALSE;
2455 if (looking_at(buf, &i, "no longer examining game *")) {
2456 if (gameMode == IcsExamining &&
2457 atoi(star_match[0]) == ics_gamenum)
2461 ics_user_moved = FALSE;
2466 /* Advance leftover_start past any newlines we find,
2467 so only partial lines can get reparsed */
2468 if (looking_at(buf, &i, "\n")) {
2469 prevColor = curColor;
2470 if (curColor != ColorNormal) {
2471 if (oldi > next_out) {
2472 SendToPlayer(&buf[next_out], oldi - next_out);
2475 Colorize(ColorNormal, FALSE);
2476 curColor = ColorNormal;
2478 if (started == STARTED_BOARD) {
2479 started = STARTED_NONE;
2480 parse[parse_pos] = NULLCHAR;
2481 ParseBoard12(parse);
2484 /* Send premove here */
2485 if (appData.premove) {
2487 if (currentMove == 0 &&
2488 gameMode == IcsPlayingWhite &&
2489 appData.premoveWhite) {
2490 sprintf(str, "%s%s\n", ics_prefix,
2491 appData.premoveWhiteText);
2492 if (appData.debugMode)
2493 fprintf(debugFP, "Sending premove:\n");
2495 } else if (currentMove == 1 &&
2496 gameMode == IcsPlayingBlack &&
2497 appData.premoveBlack) {
2498 sprintf(str, "%s%s\n", ics_prefix,
2499 appData.premoveBlackText);
2500 if (appData.debugMode)
2501 fprintf(debugFP, "Sending premove:\n");
2503 } else if (gotPremove) {
2505 ClearPremoveHighlights();
2506 if (appData.debugMode)
2507 fprintf(debugFP, "Sending premove:\n");
2508 UserMoveEvent(premoveFromX, premoveFromY,
2509 premoveToX, premoveToY,
2514 /* Usually suppress following prompt */
2515 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2516 if (looking_at(buf, &i, "*% ")) {
2517 savingComment = FALSE;
2521 } else if (started == STARTED_HOLDINGS) {
2523 char new_piece[MSG_SIZ];
2524 started = STARTED_NONE;
2525 parse[parse_pos] = NULLCHAR;
2526 if (appData.debugMode)
2527 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2528 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2529 gamenum == ics_gamenum) {
2530 if (gameInfo.variant == VariantNormal) {
2531 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2532 /* Get a move list just to see the header, which
2533 will tell us whether this is really bug or zh */
2534 if (ics_getting_history == H_FALSE) {
2535 ics_getting_history = H_REQUESTED;
2536 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2540 new_piece[0] = NULLCHAR;
2541 sscanf(parse, "game %d white [%s black [%s <- %s",
2542 &gamenum, white_holding, black_holding,
2544 white_holding[strlen(white_holding)-1] = NULLCHAR;
2545 black_holding[strlen(black_holding)-1] = NULLCHAR;
2547 if (appData.zippyPlay && first.initDone) {
2548 ZippyHoldings(white_holding, black_holding,
2552 if (tinyLayout || smallLayout) {
2553 char wh[16], bh[16];
2554 PackHolding(wh, white_holding);
2555 PackHolding(bh, black_holding);
2556 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2557 gameInfo.white, gameInfo.black);
2559 sprintf(str, "%s [%s] vs. %s [%s]",
2560 gameInfo.white, white_holding,
2561 gameInfo.black, black_holding);
2563 DrawPosition(FALSE, NULL);
2566 /* Suppress following prompt */
2567 if (looking_at(buf, &i, "*% ")) {
2568 savingComment = FALSE;
2575 i++; /* skip unparsed character and loop back */
2578 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2579 started != STARTED_HOLDINGS && i > next_out) {
2580 SendToPlayer(&buf[next_out], i - next_out);
2584 leftover_len = buf_len - leftover_start;
2585 /* if buffer ends with something we couldn't parse,
2586 reparse it after appending the next read */
2588 } else if (count == 0) {
2589 RemoveInputSource(isr);
2590 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2592 DisplayFatalError(_("Error reading from ICS"), error, 1);
2597 /* Board style 12 looks like this:
2599 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
2601 * The "<12> " is stripped before it gets to this routine. The two
2602 * trailing 0's (flip state and clock ticking) are later addition, and
2603 * some chess servers may not have them, or may have only the first.
2604 * Additional trailing fields may be added in the future.
2607 #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
2609 #define RELATION_OBSERVING_PLAYED 0
2610 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2611 #define RELATION_PLAYING_MYMOVE 1
2612 #define RELATION_PLAYING_NOTMYMOVE -1
2613 #define RELATION_EXAMINING 2
2614 #define RELATION_ISOLATED_BOARD -3
2615 #define RELATION_STARTING_POSITION -4 /* FICS only */
2618 ParseBoard12(string)
2621 GameMode newGameMode;
2622 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
2623 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
2624 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2625 char to_play, board_chars[72];
2626 char move_str[500], str[500], elapsed_time[500];
2627 char black[32], white[32];
2629 int prevMove = currentMove;
2632 int fromX, fromY, toX, toY;
2635 fromX = fromY = toX = toY = -1;
2639 if (appData.debugMode)
2640 fprintf(debugFP, _("Parsing board: %s\n"), string);
2642 move_str[0] = NULLCHAR;
2643 elapsed_time[0] = NULLCHAR;
2644 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2645 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2646 &gamenum, white, black, &relation, &basetime, &increment,
2647 &white_stren, &black_stren, &white_time, &black_time,
2648 &moveNum, str, elapsed_time, move_str, &ics_flip,
2652 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2653 DisplayError(str, 0);
2657 /* Convert the move number to internal form */
2658 moveNum = (moveNum - 1) * 2;
2659 if (to_play == 'B') moveNum++;
2660 if (moveNum >= MAX_MOVES) {
2661 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2667 case RELATION_OBSERVING_PLAYED:
2668 case RELATION_OBSERVING_STATIC:
2669 if (gamenum == -1) {
2670 /* Old ICC buglet */
2671 relation = RELATION_OBSERVING_STATIC;
2673 newGameMode = IcsObserving;
2675 case RELATION_PLAYING_MYMOVE:
2676 case RELATION_PLAYING_NOTMYMOVE:
2678 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2679 IcsPlayingWhite : IcsPlayingBlack;
2681 case RELATION_EXAMINING:
2682 newGameMode = IcsExamining;
2684 case RELATION_ISOLATED_BOARD:
2686 /* Just display this board. If user was doing something else,
2687 we will forget about it until the next board comes. */
2688 newGameMode = IcsIdle;
2690 case RELATION_STARTING_POSITION:
2691 newGameMode = gameMode;
2695 /* Modify behavior for initial board display on move listing
2698 switch (ics_getting_history) {
2702 case H_GOT_REQ_HEADER:
2703 case H_GOT_UNREQ_HEADER:
2704 /* This is the initial position of the current game */
2705 gamenum = ics_gamenum;
2706 moveNum = 0; /* old ICS bug workaround */
2707 if (to_play == 'B') {
2708 startedFromSetupPosition = TRUE;
2709 blackPlaysFirst = TRUE;
2711 if (forwardMostMove == 0) forwardMostMove = 1;
2712 if (backwardMostMove == 0) backwardMostMove = 1;
2713 if (currentMove == 0) currentMove = 1;
2715 newGameMode = gameMode;
2716 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2718 case H_GOT_UNWANTED_HEADER:
2719 /* This is an initial board that we don't want */
2721 case H_GETTING_MOVES:
2722 /* Should not happen */
2723 DisplayError(_("Error gathering move list: extra board"), 0);
2724 ics_getting_history = H_FALSE;
2728 /* Take action if this is the first board of a new game, or of a
2729 different game than is currently being displayed. */
2730 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2731 relation == RELATION_ISOLATED_BOARD) {
2733 /* Forget the old game and get the history (if any) of the new one */
2734 if (gameMode != BeginningOfGame) {
2738 if (appData.autoRaiseBoard) BoardToTop();
2740 if (gamenum == -1) {
2741 newGameMode = IcsIdle;
2742 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2743 appData.getMoveList) {
2744 /* Need to get game history */
2745 ics_getting_history = H_REQUESTED;
2746 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2750 /* Initially flip the board to have black on the bottom if playing
2751 black or if the ICS flip flag is set, but let the user change
2752 it with the Flip View button. */
2753 flipView = appData.autoFlipView ?
2754 (newGameMode == IcsPlayingBlack) || ics_flip :
2757 /* Done with values from previous mode; copy in new ones */
2758 gameMode = newGameMode;
2760 ics_gamenum = gamenum;
2761 if (gamenum == gs_gamenum) {
2762 int klen = strlen(gs_kind);
2763 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2764 sprintf(str, "ICS %s", gs_kind);
2765 gameInfo.event = StrSave(str);
2767 gameInfo.event = StrSave("ICS game");
2769 gameInfo.site = StrSave(appData.icsHost);
2770 gameInfo.date = PGNDate();
2771 gameInfo.round = StrSave("-");
2772 gameInfo.white = StrSave(white);
2773 gameInfo.black = StrSave(black);
2774 timeControl = basetime * 60 * 1000;
2775 timeIncrement = increment * 1000;
2776 movesPerSession = 0;
2777 gameInfo.timeControl = TimeControlTagValue();
2778 gameInfo.variant = StringToVariant(gameInfo.event);
2780 /* Do we have the ratings? */
2781 if (strcmp(player1Name, white) == 0 &&
2782 strcmp(player2Name, black) == 0) {
2783 if (appData.debugMode)
2784 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2785 player1Rating, player2Rating);
2786 gameInfo.whiteRating = player1Rating;
2787 gameInfo.blackRating = player2Rating;
2788 } else if (strcmp(player2Name, white) == 0 &&
2789 strcmp(player1Name, black) == 0) {
2790 if (appData.debugMode)
2791 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2792 player2Rating, player1Rating);
2793 gameInfo.whiteRating = player2Rating;
2794 gameInfo.blackRating = player1Rating;
2796 player1Name[0] = player2Name[0] = NULLCHAR;
2798 /* Silence shouts if requested */
2799 if (appData.quietPlay &&
2800 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2801 SendToICS(ics_prefix);
2802 SendToICS("set shout 0\n");
2806 /* Deal with midgame name changes */
2808 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2809 if (gameInfo.white) free(gameInfo.white);
2810 gameInfo.white = StrSave(white);
2812 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2813 if (gameInfo.black) free(gameInfo.black);
2814 gameInfo.black = StrSave(black);
2818 /* Throw away game result if anything actually changes in examine mode */
2819 if (gameMode == IcsExamining && !newGame) {
2820 gameInfo.result = GameUnfinished;
2821 if (gameInfo.resultDetails != NULL) {
2822 free(gameInfo.resultDetails);
2823 gameInfo.resultDetails = NULL;
2827 /* In pausing && IcsExamining mode, we ignore boards coming
2828 in if they are in a different variation than we are. */
2829 if (pauseExamInvalid) return;
2830 if (pausing && gameMode == IcsExamining) {
2831 if (moveNum <= pauseExamForwardMostMove) {
2832 pauseExamInvalid = TRUE;
2833 forwardMostMove = pauseExamForwardMostMove;
2838 /* Parse the board */
2839 for (k = 0; k < 8; k++)
2840 for (j = 0; j < 8; j++)
2841 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2842 CopyBoard(boards[moveNum], board);
2844 startedFromSetupPosition =
2845 !CompareBoards(board, initialPosition);
2848 if (ics_getting_history == H_GOT_REQ_HEADER ||
2849 ics_getting_history == H_GOT_UNREQ_HEADER) {
2850 /* This was an initial position from a move list, not
2851 the current position */
2855 /* Update currentMove and known move number limits */
2856 newMove = newGame || moveNum > forwardMostMove;
2858 /* If we found takebacks during icsEngineAnalyze
2859 try send to engine */
2860 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
2861 takeback = forwardMostMove - moveNum;
2862 for (i = 0; i < takeback; i++) {
2863 if (appData.debugMode) fprintf(debugFP, "take back move\n");
2864 SendToProgram("undo\n", &first);
2868 forwardMostMove = backwardMostMove = currentMove = moveNum;
2869 if (gameMode == IcsExamining && moveNum == 0) {
2870 /* Workaround for ICS limitation: we are not told the wild
2871 type when starting to examine a game. But if we ask for
2872 the move list, the move list header will tell us */
2873 ics_getting_history = H_REQUESTED;
2874 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2877 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2878 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2879 forwardMostMove = moveNum;
2880 if (!pausing || currentMove > forwardMostMove)
2881 currentMove = forwardMostMove;
2883 /* New part of history that is not contiguous with old part */
2884 if (pausing && gameMode == IcsExamining) {
2885 pauseExamInvalid = TRUE;
2886 forwardMostMove = pauseExamForwardMostMove;
2889 forwardMostMove = backwardMostMove = currentMove = moveNum;
2890 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2891 ics_getting_history = H_REQUESTED;
2892 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2897 /* Update the clocks */
2898 if (strchr(elapsed_time, '.')) {
2900 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2901 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2903 /* Time is in seconds */
2904 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2905 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2910 if (appData.zippyPlay && newGame &&
2911 gameMode != IcsObserving && gameMode != IcsIdle &&
2912 gameMode != IcsExamining)
2913 ZippyFirstBoard(moveNum, basetime, increment);
2916 /* Put the move on the move list, first converting
2917 to canonical algebraic form. */
2919 if (moveNum <= backwardMostMove) {
2920 /* We don't know what the board looked like before
2922 strcpy(parseList[moveNum - 1], move_str);
2923 strcat(parseList[moveNum - 1], " ");
2924 strcat(parseList[moveNum - 1], elapsed_time);
2925 moveList[moveNum - 1][0] = NULLCHAR;
2926 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2927 &fromX, &fromY, &toX, &toY, &promoChar)) {
2928 (void) CoordsToAlgebraic(boards[moveNum - 1],
2929 PosFlags(moveNum - 1), EP_UNKNOWN,
2930 fromY, fromX, toY, toX, promoChar,
2931 parseList[moveNum-1]);
2932 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2938 strcat(parseList[moveNum - 1], "+");
2941 strcat(parseList[moveNum - 1], "#");
2944 strcat(parseList[moveNum - 1], " ");
2945 strcat(parseList[moveNum - 1], elapsed_time);
2946 /* currentMoveString is set as a side-effect of ParseOneMove */
2947 strcpy(moveList[moveNum - 1], currentMoveString);
2948 strcat(moveList[moveNum - 1], "\n");
2949 } else if (strcmp(move_str, "none") == 0) {
2950 /* Again, we don't know what the board looked like;
2951 this is really the start of the game. */
2952 parseList[moveNum - 1][0] = NULLCHAR;
2953 moveList[moveNum - 1][0] = NULLCHAR;
2954 backwardMostMove = moveNum;
2955 startedFromSetupPosition = TRUE;
2956 fromX = fromY = toX = toY = -1;
2958 /* Move from ICS was illegal!? Punt. */
2960 if (appData.testLegality && appData.debugMode) {
2961 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2962 DisplayError(str, 0);
2965 strcpy(parseList[moveNum - 1], move_str);
2966 strcat(parseList[moveNum - 1], " ");
2967 strcat(parseList[moveNum - 1], elapsed_time);
2968 moveList[moveNum - 1][0] = NULLCHAR;
2969 fromX = fromY = toX = toY = -1;
2973 /* Send move to chess program (BEFORE animating it). */
2974 if (appData.zippyPlay && !newGame && newMove &&
2975 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2977 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2978 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2979 if (moveList[moveNum - 1][0] == NULLCHAR) {
2980 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2982 DisplayError(str, 0);
2984 if (first.sendTime) {
2985 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2987 SendMoveToProgram(moveNum - 1, &first);
2990 if (first.useColors) {
2991 SendToProgram(gameMode == IcsPlayingWhite ?
2993 "black\ngo\n", &first);
2995 SendToProgram("go\n", &first);
2997 first.maybeThinking = TRUE;
3000 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3001 if (moveList[moveNum - 1][0] == NULLCHAR) {
3002 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3003 DisplayError(str, 0);
3005 SendMoveToProgram(moveNum - 1, &first);
3012 if (moveNum > 0 && !gotPremove) {
3013 /* If move comes from a remote source, animate it. If it
3014 isn't remote, it will have already been animated. */
3015 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3016 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3018 if (!pausing && appData.highlightLastMove) {
3019 SetHighlights(fromX, fromY, toX, toY);
3023 /* Start the clocks */
3024 whiteFlag = blackFlag = FALSE;
3025 appData.clockMode = !(basetime == 0 && increment == 0);
3027 ics_clock_paused = TRUE;
3029 } else if (ticking == 1) {
3030 ics_clock_paused = FALSE;
3032 if (gameMode == IcsIdle ||
3033 relation == RELATION_OBSERVING_STATIC ||
3034 relation == RELATION_EXAMINING ||
3036 DisplayBothClocks();
3040 /* Display opponents and material strengths */
3041 if (gameInfo.variant != VariantBughouse &&
3042 gameInfo.variant != VariantCrazyhouse) {
3043 if (tinyLayout || smallLayout) {
3044 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3045 gameInfo.white, white_stren, gameInfo.black, black_stren,
3046 basetime, increment);
3048 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3049 gameInfo.white, white_stren, gameInfo.black, black_stren,
3050 basetime, increment);
3056 /* Display the board */
3059 if (appData.premove)
3061 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3062 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3063 ClearPremoveHighlights();
3065 DrawPosition(FALSE, boards[currentMove]);
3066 DisplayMove(moveNum - 1);
3067 if (appData.ringBellAfterMoves && !ics_user_moved)
3071 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3078 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3079 ics_getting_history = H_REQUESTED;
3080 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3086 AnalysisPeriodicEvent(force)
3089 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3090 && !force) || !appData.periodicUpdates)
3093 /* Send . command to Crafty to collect stats */
3094 SendToProgram(".\n", &first);
3096 /* Don't send another until we get a response (this makes
3097 us stop sending to old Crafty's which don't understand
3098 the "." command (sending illegal cmds resets node count & time,
3099 which looks bad)) */
3100 programStats.ok_to_send = 0;
3104 SendMoveToProgram(moveNum, cps)
3106 ChessProgramState *cps;
3109 if (cps->useUsermove) {
3110 SendToProgram("usermove ", cps);
3114 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3115 int len = space - parseList[moveNum];
3116 memcpy(buf, parseList[moveNum], len);
3118 buf[len] = NULLCHAR;
3120 sprintf(buf, "%s\n", parseList[moveNum]);
3122 SendToProgram(buf, cps);
3124 SendToProgram(moveList[moveNum], cps);
3129 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3131 int fromX, fromY, toX, toY;
3133 char user_move[MSG_SIZ];
3137 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3138 (int)moveType, fromX, fromY, toX, toY);
3139 DisplayError(user_move + strlen("say "), 0);
3141 case WhiteKingSideCastle:
3142 case BlackKingSideCastle:
3143 case WhiteQueenSideCastleWild:
3144 case BlackQueenSideCastleWild:
3145 sprintf(user_move, "o-o\n");
3147 case WhiteQueenSideCastle:
3148 case BlackQueenSideCastle:
3149 case WhiteKingSideCastleWild:
3150 case BlackKingSideCastleWild:
3151 sprintf(user_move, "o-o-o\n");
3153 case WhitePromotionQueen:
3154 case BlackPromotionQueen:
3155 case WhitePromotionRook:
3156 case BlackPromotionRook:
3157 case WhitePromotionBishop:
3158 case BlackPromotionBishop:
3159 case WhitePromotionKnight:
3160 case BlackPromotionKnight:
3161 case WhitePromotionKing:
3162 case BlackPromotionKing:
3163 sprintf(user_move, "%c%c%c%c=%c\n",
3164 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3165 PieceToChar(PromoPiece(moveType)));
3169 sprintf(user_move, "%c@%c%c\n",
3170 ToUpper(PieceToChar((ChessSquare) fromX)),
3171 'a' + toX, '1' + toY);
3174 case WhiteCapturesEnPassant:
3175 case BlackCapturesEnPassant:
3176 case IllegalMove: /* could be a variant we don't quite understand */
3177 sprintf(user_move, "%c%c%c%c\n",
3178 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3181 SendToICS(user_move);
3185 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3190 if (rf == DROP_RANK) {
3191 sprintf(move, "%c@%c%c\n",
3192 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3194 if (promoChar == 'x' || promoChar == NULLCHAR) {
3195 sprintf(move, "%c%c%c%c\n",
3196 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3198 sprintf(move, "%c%c%c%c%c\n",
3199 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3205 ProcessICSInitScript(f)
3210 while (fgets(buf, MSG_SIZ, f)) {
3211 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3218 /* Parser for moves from gnuchess, ICS, or user typein box */
3220 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3223 ChessMove *moveType;
3224 int *fromX, *fromY, *toX, *toY;
3227 *moveType = yylexstr(moveNum, move);
3228 switch (*moveType) {
3229 case WhitePromotionQueen:
3230 case BlackPromotionQueen:
3231 case WhitePromotionRook:
3232 case BlackPromotionRook:
3233 case WhitePromotionBishop:
3234 case BlackPromotionBishop:
3235 case WhitePromotionKnight:
3236 case BlackPromotionKnight:
3237 case WhitePromotionKing:
3238 case BlackPromotionKing:
3240 case WhiteCapturesEnPassant:
3241 case BlackCapturesEnPassant:
3242 case WhiteKingSideCastle:
3243 case WhiteQueenSideCastle:
3244 case BlackKingSideCastle:
3245 case BlackQueenSideCastle:
3246 case WhiteKingSideCastleWild:
3247 case WhiteQueenSideCastleWild:
3248 case BlackKingSideCastleWild:
3249 case BlackQueenSideCastleWild:
3250 case IllegalMove: /* bug or odd chess variant */
3251 *fromX = currentMoveString[0] - 'a';
3252 *fromY = currentMoveString[1] - '1';
3253 *toX = currentMoveString[2] - 'a';
3254 *toY = currentMoveString[3] - '1';
3255 *promoChar = currentMoveString[4];
3256 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3257 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3258 *fromX = *fromY = *toX = *toY = 0;
3261 if (appData.testLegality) {
3262 return (*moveType != IllegalMove);
3264 return !(fromX == fromY && toX == toY);
3269 *fromX = *moveType == WhiteDrop ?
3270 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3271 (int) CharToPiece(ToLower(currentMoveString[0]));
3273 *toX = currentMoveString[2] - 'a';
3274 *toY = currentMoveString[3] - '1';
3275 *promoChar = NULLCHAR;
3279 case ImpossibleMove:
3280 case (ChessMove) 0: /* end of file */
3290 *fromX = *fromY = *toX = *toY = 0;
3291 *promoChar = NULLCHAR;
3298 InitPosition(redraw)
3301 currentMove = forwardMostMove = backwardMostMove = 0;
3302 switch (gameInfo.variant) {
3304 CopyBoard(boards[0], initialPosition);
3306 case VariantTwoKings:
3307 CopyBoard(boards[0], twoKingsPosition);
3308 startedFromSetupPosition = TRUE;
3310 case VariantWildCastle:
3311 CopyBoard(boards[0], initialPosition);
3312 /* !!?shuffle with kings guaranteed to be on d or e file */
3314 case VariantNoCastle:
3315 CopyBoard(boards[0], initialPosition);
3316 /* !!?unconstrained back-rank shuffle */
3318 case VariantFischeRandom:
3319 CopyBoard(boards[0], initialPosition);
3320 /* !!shuffle according to FR rules */
3324 DrawPosition(FALSE, boards[currentMove]);
3328 SendBoard(cps, moveNum)
3329 ChessProgramState *cps;
3332 char message[MSG_SIZ];
3334 if (cps->useSetboard) {
3335 char* fen = PositionToFEN(moveNum);
3336 sprintf(message, "setboard %s\n", fen);
3337 SendToProgram(message, cps);
3343 /* Kludge to set black to move, avoiding the troublesome and now
3344 * deprecated "black" command.
3346 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3348 SendToProgram("edit\n", cps);
3349 SendToProgram("#\n", cps);
3350 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3351 bp = &boards[moveNum][i][0];
3352 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3353 if ((int) *bp < (int) BlackPawn) {
3354 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3356 SendToProgram(message, cps);
3361 SendToProgram("c\n", cps);
3362 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3363 bp = &boards[moveNum][i][0];
3364 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3365 if (((int) *bp != (int) EmptySquare)
3366 && ((int) *bp >= (int) BlackPawn)) {
3367 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3369 SendToProgram(message, cps);
3374 SendToProgram(".\n", cps);
3379 IsPromotion(fromX, fromY, toX, toY)
3380 int fromX, fromY, toX, toY;
3382 return gameMode != EditPosition &&
3383 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3384 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3385 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3390 PieceForSquare (x, y)
3394 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3397 return boards[currentMove][y][x];
3401 OKToStartUserMove(x, y)
3404 ChessSquare from_piece;
3407 if (matchMode) return FALSE;
3408 if (gameMode == EditPosition) return TRUE;
3410 if (x >= 0 && y >= 0)
3411 from_piece = boards[currentMove][y][x];
3413 from_piece = EmptySquare;
3415 if (from_piece == EmptySquare) return FALSE;
3417 white_piece = (int)from_piece >= (int)WhitePawn &&
3418 (int)from_piece <= (int)WhiteKing;
3421 case PlayFromGameFile:
3423 case TwoMachinesPlay:
3431 case MachinePlaysWhite:
3432 case IcsPlayingBlack:
3433 if (appData.zippyPlay) return FALSE;
3435 DisplayMoveError(_("You are playing Black"));
3440 case MachinePlaysBlack:
3441 case IcsPlayingWhite:
3442 if (appData.zippyPlay) return FALSE;
3444 DisplayMoveError(_("You are playing White"));
3450 if (!white_piece && WhiteOnMove(currentMove)) {
3451 DisplayMoveError(_("It is White's turn"));
3454 if (white_piece && !WhiteOnMove(currentMove)) {
3455 DisplayMoveError(_("It is Black's turn"));
3458 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3459 /* Editing correspondence game history */
3460 /* Could disallow this or prompt for confirmation */
3463 if (currentMove < forwardMostMove) {
3464 /* Discarding moves */
3465 /* Could prompt for confirmation here,
3466 but I don't think that's such a good idea */
3467 forwardMostMove = currentMove;
3471 case BeginningOfGame:
3472 if (appData.icsActive) return FALSE;
3473 if (!appData.noChessProgram) {
3475 DisplayMoveError(_("You are playing White"));
3482 if (!white_piece && WhiteOnMove(currentMove)) {
3483 DisplayMoveError(_("It is White's turn"));
3486 if (white_piece && !WhiteOnMove(currentMove)) {
3487 DisplayMoveError(_("It is Black's turn"));
3496 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3497 && gameMode != AnalyzeFile && gameMode != Training) {
3498 DisplayMoveError(_("Displayed position is not current"));
3504 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3505 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3506 int lastLoadGameUseList = FALSE;
3507 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3508 ChessMove lastLoadGameStart = (ChessMove) 0;
3512 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3513 int fromX, fromY, toX, toY;
3518 if (fromX < 0 || fromY < 0) return;
3519 if ((fromX == toX) && (fromY == toY)) {
3523 /* Check if the user is playing in turn. This is complicated because we
3524 let the user "pick up" a piece before it is his turn. So the piece he
3525 tried to pick up may have been captured by the time he puts it down!
3526 Therefore we use the color the user is supposed to be playing in this
3527 test, not the color of the piece that is currently on the starting
3528 square---except in EditGame mode, where the user is playing both
3529 sides; fortunately there the capture race can't happen. (It can
3530 now happen in IcsExamining mode, but that's just too bad. The user
3531 will get a somewhat confusing message in that case.)
3535 case PlayFromGameFile:
3537 case TwoMachinesPlay:
3541 /* We switched into a game mode where moves are not accepted,
3542 perhaps while the mouse button was down. */
3545 case MachinePlaysWhite:
3546 /* User is moving for Black */
3547 if (WhiteOnMove(currentMove)) {
3548 DisplayMoveError(_("It is White's turn"));
3553 case MachinePlaysBlack:
3554 /* User is moving for White */
3555 if (!WhiteOnMove(currentMove)) {
3556 DisplayMoveError(_("It is Black's turn"));
3563 case BeginningOfGame:
3566 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3567 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3568 /* User is moving for Black */
3569 if (WhiteOnMove(currentMove)) {
3570 DisplayMoveError(_("It is White's turn"));
3574 /* User is moving for White */
3575 if (!WhiteOnMove(currentMove)) {
3576 DisplayMoveError(_("It is Black's turn"));
3582 case IcsPlayingBlack:
3583 /* User is moving for Black */
3584 if (WhiteOnMove(currentMove)) {
3585 if (!appData.premove) {
3586 DisplayMoveError(_("It is White's turn"));
3587 } else if (toX >= 0 && toY >= 0) {
3590 premoveFromX = fromX;
3591 premoveFromY = fromY;
3592 premovePromoChar = promoChar;
3594 if (appData.debugMode)
3595 fprintf(debugFP, "Got premove: fromX %d,"
3596 "fromY %d, toX %d, toY %d\n",
3597 fromX, fromY, toX, toY);
3603 case IcsPlayingWhite:
3604 /* User is moving for White */
3605 if (!WhiteOnMove(currentMove)) {
3606 if (!appData.premove) {
3607 DisplayMoveError(_("It is Black's turn"));
3608 } else if (toX >= 0 && toY >= 0) {
3611 premoveFromX = fromX;
3612 premoveFromY = fromY;
3613 premovePromoChar = promoChar;
3615 if (appData.debugMode)
3616 fprintf(debugFP, "Got premove: fromX %d,"
3617 "fromY %d, toX %d, toY %d\n",
3618 fromX, fromY, toX, toY);
3628 if (toX == -2 || toY == -2) {
3629 boards[0][fromY][fromX] = EmptySquare;
3630 DrawPosition(FALSE, boards[currentMove]);
3631 } else if (toX >= 0 && toY >= 0) {
3632 boards[0][toY][toX] = boards[0][fromY][fromX];
3633 boards[0][fromY][fromX] = EmptySquare;
3634 DrawPosition(FALSE, boards[currentMove]);
3639 if (toX < 0 || toY < 0) return;
3640 userOfferedDraw = FALSE;
3642 if (appData.testLegality) {
3643 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3644 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3645 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3646 DisplayMoveError(_("Illegal move"));
3650 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3653 if (gameMode == Training) {
3654 /* compare the move played on the board to the next move in the
3655 * game. If they match, display the move and the opponent's response.
3656 * If they don't match, display an error message.
3660 CopyBoard(testBoard, boards[currentMove]);
3661 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3663 if (CompareBoards(testBoard, boards[currentMove+1])) {
3664 ForwardInner(currentMove+1);
3666 /* Autoplay the opponent's response.
3667 * if appData.animate was TRUE when Training mode was entered,
3668 * the response will be animated.
3670 saveAnimate = appData.animate;
3671 appData.animate = animateTraining;
3672 ForwardInner(currentMove+1);
3673 appData.animate = saveAnimate;
3675 /* check for the end of the game */
3676 if (currentMove >= forwardMostMove) {
3677 gameMode = PlayFromGameFile;
3679 SetTrainingModeOff();
3680 DisplayInformation(_("End of game"));
3683 DisplayError(_("Incorrect move"), 0);
3688 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3691 /* Common tail of UserMoveEvent and DropMenuEvent */
3693 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3695 int fromX, fromY, toX, toY;
3696 /*char*/int promoChar;
3698 /* Ok, now we know that the move is good, so we can kill
3699 the previous line in Analysis Mode */
3700 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3701 forwardMostMove = currentMove;
3704 /* If we need the chess program but it's dead, restart it */
3705 ResurrectChessProgram();
3707 /* A user move restarts a paused game*/
3711 thinkOutput[0] = NULLCHAR;
3713 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3715 if (gameMode == BeginningOfGame) {
3716 if (appData.noChessProgram) {
3717 gameMode = EditGame;
3721 gameMode = MachinePlaysBlack;
3723 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3725 if (first.sendName) {
3726 sprintf(buf, "name %s\n", gameInfo.white);
3727 SendToProgram(buf, &first);
3733 /* Relay move to ICS or chess engine */
3734 if (appData.icsActive) {
3735 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3736 gameMode == IcsExamining) {
3737 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3741 if (first.sendTime && (gameMode == BeginningOfGame ||
3742 gameMode == MachinePlaysWhite ||
3743 gameMode == MachinePlaysBlack)) {
3744 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3746 SendMoveToProgram(forwardMostMove-1, &first);
3747 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3748 first.maybeThinking = TRUE;
3750 if (currentMove == cmailOldMove + 1) {
3751 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3755 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3759 switch (MateTest(boards[currentMove], PosFlags(currentMove),
3765 if (WhiteOnMove(currentMove)) {
3766 GameEnds(BlackWins, "Black mates", GE_PLAYER);
3768 GameEnds(WhiteWins, "White mates", GE_PLAYER);
3772 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3777 case MachinePlaysBlack:
3778 case MachinePlaysWhite:
3779 /* disable certain menu options while machine is thinking */
3780 SetMachineThinkingEnables();
3789 HandleMachineMove(message, cps)
3791 ChessProgramState *cps;
3793 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3794 char realname[MSG_SIZ];
3795 int fromX, fromY, toX, toY;
3802 * Kludge to ignore BEL characters
3804 while (*message == '\007') message++;
3807 * Look for book output
3809 if (cps == &first && bookRequested) {
3810 if (message[0] == '\t' || message[0] == ' ') {
3811 /* Part of the book output is here; append it */
3812 strcat(bookOutput, message);
3813 strcat(bookOutput, " \n");
3815 } else if (bookOutput[0] != NULLCHAR) {
3816 /* All of book output has arrived; display it */
3817 char *p = bookOutput;
3818 while (*p != NULLCHAR) {
3819 if (*p == '\t') *p = ' ';
3822 DisplayInformation(bookOutput);
3823 bookRequested = FALSE;
3824 /* Fall through to parse the current output */
3829 * Look for machine move.
3831 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3832 strcmp(buf2, "...") == 0) ||
3833 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3834 strcmp(buf1, "move") == 0)) {
3836 /* This method is only useful on engines that support ping */
3837 if (cps->lastPing != cps->lastPong) {
3838 if (gameMode == BeginningOfGame) {
3839 /* Extra move from before last new; ignore */
3840 if (appData.debugMode) {
3841 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3844 if (appData.debugMode) {
3845 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3846 cps->which, gameMode);
3848 SendToProgram("undo\n", cps);
3854 case BeginningOfGame:
3855 /* Extra move from before last reset; ignore */
3856 if (appData.debugMode) {
3857 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3864 /* Extra move after we tried to stop. The mode test is
3865 not a reliable way of detecting this problem, but it's
3866 the best we can do on engines that don't support ping.
3868 if (appData.debugMode) {
3869 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3870 cps->which, gameMode);
3872 SendToProgram("undo\n", cps);
3875 case MachinePlaysWhite:
3876 case IcsPlayingWhite:
3877 machineWhite = TRUE;
3880 case MachinePlaysBlack:
3881 case IcsPlayingBlack:
3882 machineWhite = FALSE;
3885 case TwoMachinesPlay:
3886 machineWhite = (cps->twoMachinesColor[0] == 'w');
3889 if (WhiteOnMove(forwardMostMove) != machineWhite) {
3890 if (appData.debugMode) {
3892 "Ignoring move out of turn by %s, gameMode %d"
3893 ", forwardMost %d\n",
3894 cps->which, gameMode, forwardMostMove);
3899 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3900 &fromX, &fromY, &toX, &toY, &promoChar)) {
3901 /* Machine move could not be parsed; ignore it. */
3902 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3903 machineMove, cps->which);
3904 DisplayError(buf1, 0);
3905 if (gameMode == TwoMachinesPlay) {
3906 GameEnds(machineWhite ? BlackWins : WhiteWins,
3907 "Forfeit due to illegal move", GE_XBOARD);
3912 hintRequested = FALSE;
3913 lastHint[0] = NULLCHAR;
3914 bookRequested = FALSE;
3915 /* Program may be pondering now */
3916 cps->maybeThinking = TRUE;
3917 if (cps->sendTime == 2) cps->sendTime = 1;
3918 if (cps->offeredDraw) cps->offeredDraw--;
3921 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3923 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3927 /* currentMoveString is set as a side-effect of ParseOneMove */
3928 strcpy(machineMove, currentMoveString);
3929 strcat(machineMove, "\n");
3930 strcpy(moveList[forwardMostMove], machineMove);