2 * backend.c -- Common back end for X and Windows NT versions of
5 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
6 * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
8 * The following terms apply to Digital Equipment Corporation's copyright
10 * ------------------------------------------------------------------------
13 * Permission to use, copy, modify, and distribute this software and its
14 * documentation for any purpose and without fee is hereby granted,
15 * provided that the above copyright notice appear in all copies and that
16 * both that copyright notice and this permission notice appear in
17 * supporting documentation, and that the name of Digital not be
18 * used in advertising or publicity pertaining to distribution of the
19 * software without specific, written prior permission.
21 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
22 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
23 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
24 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
25 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
26 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
28 * ------------------------------------------------------------------------
30 * The following terms apply to the enhanced version of XBoard distributed
31 * by the Free Software Foundation:
32 * ------------------------------------------------------------------------
33 * This program is free software; you can redistribute it and/or modify
34 * it under the terms of the GNU General Public License as published by
35 * the Free Software Foundation; either version 2 of the License, or
36 * (at your option) any later version.
38 * This program is distributed in the hope that it will be useful,
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 * GNU General Public License for more details.
43 * You should have received a copy of the GNU General Public License
44 * along with this program; if not, write to the Free Software
45 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
46 * ------------------------------------------------------------------------
48 * See the file ChangeLog for a revision history. */
55 #include <sys/types.h>
62 #else /* not STDC_HEADERS */
65 # else /* not HAVE_STRING_H */
67 # endif /* not HAVE_STRING_H */
68 #endif /* not STDC_HEADERS */
71 # include <sys/fcntl.h>
72 #else /* not HAVE_SYS_FCNTL_H */
75 # endif /* HAVE_FCNTL_H */
76 #endif /* not HAVE_SYS_FCNTL_H */
78 #if TIME_WITH_SYS_TIME
79 # include <sys/time.h>
83 # include <sys/time.h>
89 #if defined(_amigados) && !defined(__GNUC__)
94 extern int gettimeofday(struct timeval *, struct timezone *);
102 #include "frontend.h"
109 #include "backendz.h"
113 # define _(s) gettext (s)
114 # define N_(s) gettext_noop (s)
121 /* A point in time */
123 long sec; /* Assuming this is >= 32 bits */
124 int ms; /* Assuming this is >= 16 bits */
127 /* Search stats from chessprogram */
129 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
130 int depth; /* Current search depth */
131 int nr_moves; /* Total nr of root moves */
132 int moves_left; /* Moves remaining to be searched */
133 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
134 unsigned long nodes; /* # of nodes searched */
135 int time; /* Search time (centiseconds) */
136 int score; /* Score (centipawns) */
137 int got_only_move; /* If last msg was "(only move)" */
138 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
139 int ok_to_send; /* handshaking between send & recv */
140 int line_is_book; /* 1 if movelist is book moves */
141 int seen_stat; /* 1 if we've seen the stat01: line */
144 int establish P((void));
145 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
146 char *buf, int count, int error));
147 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void SendToICS P((char *s));
150 void SendToICSDelayed P((char *s, long msdelay));
151 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153 void InitPosition P((int redraw));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps));
216 extern int tinyLayout, smallLayout;
217 static ChessProgramStats programStats;
219 /* States for ics_getting_history */
221 #define H_REQUESTED 1
222 #define H_GOT_REQ_HEADER 2
223 #define H_GOT_UNREQ_HEADER 3
224 #define H_GETTING_MOVES 4
225 #define H_GOT_UNWANTED_HEADER 5
227 /* whosays values for GameEnds */
234 /* Maximum number of games in a cmail message */
235 #define CMAIL_MAX_GAMES 20
237 /* Different types of move when calling RegisterMove */
239 #define CMAIL_RESIGN 1
241 #define CMAIL_ACCEPT 3
243 /* Different types of result to remember for each game */
244 #define CMAIL_NOT_RESULT 0
245 #define CMAIL_OLD_RESULT 1
246 #define CMAIL_NEW_RESULT 2
248 /* Telnet protocol constants */
258 /* Fake up flags for now, as we aren't keeping track of castling
263 int flags = F_ALL_CASTLE_OK;
264 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
265 switch (gameInfo.variant) {
267 case VariantGiveaway:
268 flags |= F_IGNORE_CHECK;
269 flags &= ~F_ALL_CASTLE_OK;
272 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
274 case VariantKriegspiel:
275 flags |= F_KRIEGSPIEL_CAPTURE;
277 case VariantNoCastle:
278 flags &= ~F_ALL_CASTLE_OK;
286 FILE *gameFileFP, *debugFP;
288 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
289 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
290 char thinkOutput1[MSG_SIZ*10];
292 ChessProgramState first, second;
294 /* premove variables */
297 int premoveFromX = 0;
298 int premoveFromY = 0;
299 int premovePromoChar = 0;
301 Boolean alarmSounded;
302 /* end premove variables */
304 #define ICS_GENERIC 0
307 #define ICS_CHESSNET 3 /* not really supported */
308 int ics_type = ICS_GENERIC;
309 char *ics_prefix = "$";
311 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
312 int pauseExamForwardMostMove = 0;
313 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
314 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
315 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
316 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
317 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
318 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
319 int whiteFlag = FALSE, blackFlag = FALSE;
320 int userOfferedDraw = FALSE;
321 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
322 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
323 int cmailMoveType[CMAIL_MAX_GAMES];
324 long ics_clock_paused = 0;
325 ProcRef icsPR = NoProc, cmailPR = NoProc;
326 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
327 GameMode gameMode = BeginningOfGame;
328 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
329 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
330 char white_holding[64], black_holding[64];
331 TimeMark lastNodeCountTime;
332 long lastNodeCount=0;
333 int have_sent_ICS_logon = 0;
335 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
336 long timeRemaining[2][MAX_MOVES];
338 TimeMark programStartTime;
339 char ics_handle[MSG_SIZ];
340 int have_set_title = 0;
342 /* animateTraining preserves the state of appData.animate
343 * when Training mode is activated. This allows the
344 * response to be animated when appData.animate == TRUE and
345 * appData.animateDragging == TRUE.
347 Boolean animateTraining;
353 Board boards[MAX_MOVES];
354 Board initialPosition = {
355 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
356 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
357 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
358 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
359 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
360 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
361 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
362 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
363 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
364 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
365 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
366 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
367 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
368 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
369 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
370 BlackKing, BlackBishop, BlackKnight, BlackRook }
372 Board twoKingsPosition = {
373 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
374 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
375 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
376 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
377 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
378 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
379 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
380 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
381 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
382 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
383 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
384 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
385 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
386 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
387 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
388 BlackKing, BlackKing, BlackKnight, BlackRook }
392 /* Convert str to a rating. Checks for special cases of "----",
393 "++++", etc. Also strips ()'s */
395 string_to_rating(str)
398 while(*str && !isdigit(*str)) ++str;
400 return 0; /* One of the special "no rating" cases */
408 /* Init programStats */
409 programStats.movelist[0] = 0;
410 programStats.depth = 0;
411 programStats.nr_moves = 0;
412 programStats.moves_left = 0;
413 programStats.nodes = 0;
414 programStats.time = 100;
415 programStats.score = 0;
416 programStats.got_only_move = 0;
417 programStats.got_fail = 0;
418 programStats.line_is_book = 0;
424 int matched, min, sec;
426 GetTimeMark(&programStartTime);
429 programStats.ok_to_send = 1;
430 programStats.seen_stat = 0;
433 * Initialize game list
439 * Internet chess server status
441 if (appData.icsActive) {
442 appData.matchMode = FALSE;
443 appData.matchGames = 0;
445 appData.noChessProgram = !appData.zippyPlay;
447 appData.zippyPlay = FALSE;
448 appData.zippyTalk = FALSE;
449 appData.noChessProgram = TRUE;
451 if (*appData.icsHelper != NULLCHAR) {
452 appData.useTelnet = TRUE;
453 appData.telnetProgram = appData.icsHelper;
456 appData.zippyTalk = appData.zippyPlay = FALSE;
460 * Parse timeControl resource
462 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
463 appData.movesPerSession)) {
465 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
466 DisplayFatalError(buf, 0, 2);
470 * Parse searchTime resource
472 if (*appData.searchTime != NULLCHAR) {
473 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
475 searchTime = min * 60;
476 } else if (matched == 2) {
477 searchTime = min * 60 + sec;
480 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
481 DisplayFatalError(buf, 0, 2);
485 first.which = "first";
486 second.which = "second";
487 first.maybeThinking = second.maybeThinking = FALSE;
488 first.pr = second.pr = NoProc;
489 first.isr = second.isr = NULL;
490 first.sendTime = second.sendTime = 2;
491 first.sendDrawOffers = 1;
492 if (appData.firstPlaysBlack) {
493 first.twoMachinesColor = "black\n";
494 second.twoMachinesColor = "white\n";
496 first.twoMachinesColor = "white\n";
497 second.twoMachinesColor = "black\n";
499 first.program = appData.firstChessProgram;
500 second.program = appData.secondChessProgram;
501 first.host = appData.firstHost;
502 second.host = appData.secondHost;
503 first.dir = appData.firstDirectory;
504 second.dir = appData.secondDirectory;
505 first.other = &second;
506 second.other = &first;
507 first.initString = appData.initString;
508 second.initString = appData.secondInitString;
509 first.computerString = appData.firstComputerString;
510 second.computerString = appData.secondComputerString;
511 first.useSigint = second.useSigint = TRUE;
512 first.useSigterm = second.useSigterm = TRUE;
513 first.reuse = appData.reuseFirst;
514 second.reuse = appData.reuseSecond;
515 first.useSetboard = second.useSetboard = FALSE;
516 first.useSAN = second.useSAN = FALSE;
517 first.usePing = second.usePing = FALSE;
518 first.lastPing = second.lastPing = 0;
519 first.lastPong = second.lastPong = 0;
520 first.usePlayother = second.usePlayother = FALSE;
521 first.useColors = second.useColors = TRUE;
522 first.useUsermove = second.useUsermove = FALSE;
523 first.sendICS = second.sendICS = FALSE;
524 first.sendName = second.sendName = appData.icsActive;
525 first.sdKludge = second.sdKludge = FALSE;
526 first.stKludge = second.stKludge = FALSE;
527 TidyProgramName(first.program, first.host, first.tidy);
528 TidyProgramName(second.program, second.host, second.tidy);
529 first.matchWins = second.matchWins = 0;
530 strcpy(first.variants, appData.variant);
531 strcpy(second.variants, appData.variant);
532 first.analysisSupport = second.analysisSupport = 2; /* detect */
533 first.analyzing = second.analyzing = FALSE;
534 first.initDone = second.initDone = FALSE;
536 if (appData.firstProtocolVersion > PROTOVER ||
537 appData.firstProtocolVersion < 1) {
539 sprintf(buf, _("protocol version %d not supported"),
540 appData.firstProtocolVersion);
541 DisplayFatalError(buf, 0, 2);
543 first.protocolVersion = appData.firstProtocolVersion;
546 if (appData.secondProtocolVersion > PROTOVER ||
547 appData.secondProtocolVersion < 1) {
549 sprintf(buf, _("protocol version %d not supported"),
550 appData.secondProtocolVersion);
551 DisplayFatalError(buf, 0, 2);
553 second.protocolVersion = appData.secondProtocolVersion;
556 if (appData.icsActive) {
557 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
558 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
559 appData.clockMode = FALSE;
560 first.sendTime = second.sendTime = 0;
564 /* Override some settings from environment variables, for backward
565 compatibility. Unfortunately it's not feasible to have the env
566 vars just set defaults, at least in xboard. Ugh.
568 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
573 if (appData.noChessProgram) {
574 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
575 + strlen(PATCHLEVEL));
576 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
580 while (*q != ' ' && *q != NULLCHAR) q++;
582 while (p > first.program && *(p-1) != '/') p--;
583 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
584 + strlen(PATCHLEVEL) + (q - p));
585 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
586 strncat(programVersion, p, q - p);
589 if (!appData.icsActive) {
591 /* Check for variants that are supported only in ICS mode,
592 or not at all. Some that are accepted here nevertheless
593 have bugs; see comments below.
595 VariantClass variant = StringToVariant(appData.variant);
597 case VariantBughouse: /* need four players and two boards */
598 case VariantKriegspiel: /* need to hide pieces and move details */
599 case VariantFischeRandom: /* castling doesn't work, shuffle not done */
600 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
601 DisplayFatalError(buf, 0, 2);
605 case VariantLoadable:
615 sprintf(buf, _("Unknown variant name %s"), appData.variant);
616 DisplayFatalError(buf, 0, 2);
619 case VariantNormal: /* definitely works! */
620 case VariantWildCastle: /* pieces not automatically shuffled */
621 case VariantNoCastle: /* pieces not automatically shuffled */
622 case VariantCrazyhouse: /* holdings not shown,
623 offboard interposition not understood */
624 case VariantLosers: /* should work except for win condition,
625 and doesn't know captures are mandatory */
626 case VariantSuicide: /* should work except for win condition,
627 and doesn't know captures are mandatory */
628 case VariantGiveaway: /* should work except for win condition,
629 and doesn't know captures are mandatory */
630 case VariantTwoKings: /* should work */
631 case VariantAtomic: /* should work except for win condition */
632 case Variant3Check: /* should work except for win condition */
633 case VariantShatranj: /* might work if TestLegality is off */
640 ParseTimeControl(tc, ti, mps)
645 int matched, min, sec;
647 matched = sscanf(tc, "%d:%d", &min, &sec);
649 timeControl = min * 60 * 1000;
650 } else if (matched == 2) {
651 timeControl = (min * 60 + sec) * 1000;
657 timeIncrement = ti * 1000; /* convert to ms */
661 movesPerSession = mps;
669 if (appData.debugMode) {
670 fprintf(debugFP, "%s\n", programVersion);
673 if (appData.matchGames > 0) {
674 appData.matchMode = TRUE;
675 } else if (appData.matchMode) {
676 appData.matchGames = 1;
679 if (appData.noChessProgram || first.protocolVersion == 1) {
682 /* kludge: allow timeout for initial "feature" commands */
684 DisplayMessage("", "Starting chess program");
685 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
690 InitBackEnd3 P((void))
692 GameMode initialMode;
696 InitChessProgram(&first);
698 if (appData.icsActive) {
701 if (*appData.icsCommPort != NULLCHAR) {
702 sprintf(buf, _("Could not open comm port %s"),
703 appData.icsCommPort);
705 sprintf(buf, _("Could not connect to host %s, port %s"),
706 appData.icsHost, appData.icsPort);
708 DisplayFatalError(buf, err, 1);
713 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
715 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
716 } else if (appData.noChessProgram) {
722 if (*appData.cmailGameName != NULLCHAR) {
724 OpenLoopback(&cmailPR);
726 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
730 DisplayMessage("", "");
731 if (StrCaseCmp(appData.initialMode, "") == 0) {
732 initialMode = BeginningOfGame;
733 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
734 initialMode = TwoMachinesPlay;
735 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
736 initialMode = AnalyzeFile;
737 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
738 initialMode = AnalyzeMode;
739 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
740 initialMode = MachinePlaysWhite;
741 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
742 initialMode = MachinePlaysBlack;
743 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
744 initialMode = EditGame;
745 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
746 initialMode = EditPosition;
747 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
748 initialMode = Training;
750 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
751 DisplayFatalError(buf, 0, 2);
755 if (appData.matchMode) {
756 /* Set up machine vs. machine match */
757 if (appData.noChessProgram) {
758 DisplayFatalError(_("Can't have a match with no chess programs"),
764 if (*appData.loadGameFile != NULLCHAR) {
765 if (!LoadGameFromFile(appData.loadGameFile,
766 appData.loadGameIndex,
767 appData.loadGameFile, FALSE)) {
768 DisplayFatalError(_("Bad game file"), 0, 1);
771 } else if (*appData.loadPositionFile != NULLCHAR) {
772 if (!LoadPositionFromFile(appData.loadPositionFile,
773 appData.loadPositionIndex,
774 appData.loadPositionFile)) {
775 DisplayFatalError(_("Bad position file"), 0, 1);
780 } else if (*appData.cmailGameName != NULLCHAR) {
781 /* Set up cmail mode */
782 ReloadCmailMsgEvent(TRUE);
784 /* Set up other modes */
785 if (initialMode == AnalyzeFile) {
786 if (*appData.loadGameFile == NULLCHAR) {
787 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
791 if (*appData.loadGameFile != NULLCHAR) {
792 (void) LoadGameFromFile(appData.loadGameFile,
793 appData.loadGameIndex,
794 appData.loadGameFile, TRUE);
795 } else if (*appData.loadPositionFile != NULLCHAR) {
796 (void) LoadPositionFromFile(appData.loadPositionFile,
797 appData.loadPositionIndex,
798 appData.loadPositionFile);
800 if (initialMode == AnalyzeMode) {
801 if (appData.noChessProgram) {
802 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
805 if (appData.icsActive) {
806 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
810 } else if (initialMode == AnalyzeFile) {
811 ShowThinkingEvent(TRUE);
813 AnalysisPeriodicEvent(1);
814 } else if (initialMode == MachinePlaysWhite) {
815 if (appData.noChessProgram) {
816 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
820 if (appData.icsActive) {
821 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
826 } else if (initialMode == MachinePlaysBlack) {
827 if (appData.noChessProgram) {
828 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
832 if (appData.icsActive) {
833 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
838 } else if (initialMode == TwoMachinesPlay) {
839 if (appData.noChessProgram) {
840 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
844 if (appData.icsActive) {
845 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
850 } else if (initialMode == EditGame) {
852 } else if (initialMode == EditPosition) {
854 } else if (initialMode == Training) {
855 if (*appData.loadGameFile == NULLCHAR) {
856 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
865 * Establish will establish a contact to a remote host.port.
866 * Sets icsPR to a ProcRef for a process (or pseudo-process)
867 * used to talk to the host.
868 * Returns 0 if okay, error code if not.
875 if (*appData.icsCommPort != NULLCHAR) {
876 /* Talk to the host through a serial comm port */
877 return OpenCommPort(appData.icsCommPort, &icsPR);
879 } else if (*appData.gateway != NULLCHAR) {
880 if (*appData.remoteShell == NULLCHAR) {
881 /* Use the rcmd protocol to run telnet program on a gateway host */
882 sprintf(buf, "%s %s %s",
883 appData.telnetProgram, appData.icsHost, appData.icsPort);
884 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
887 /* Use the rsh program to run telnet program on a gateway host */
888 if (*appData.remoteUser == NULLCHAR) {
889 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
890 appData.gateway, appData.telnetProgram,
891 appData.icsHost, appData.icsPort);
893 sprintf(buf, "%s %s -l %s %s %s %s",
894 appData.remoteShell, appData.gateway,
895 appData.remoteUser, appData.telnetProgram,
896 appData.icsHost, appData.icsPort);
898 return StartChildProcess(buf, "", &icsPR);
901 } else if (appData.useTelnet) {
902 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
905 /* TCP socket interface differs somewhat between
906 Unix and NT; handle details in the front end.
908 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
913 show_bytes(fp, buf, count)
919 if (*buf < 040 || *(unsigned char *) buf > 0177) {
920 fprintf(fp, "\\%03o", *buf & 0xff);
929 /* Returns an errno value */
931 OutputMaybeTelnet(pr, message, count, outError)
937 char buf[8192], *p, *q, *buflim;
938 int left, newcount, outcount;
940 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
941 *appData.gateway != NULLCHAR) {
942 if (appData.debugMode) {
943 fprintf(debugFP, ">ICS: ");
944 show_bytes(debugFP, message, count);
945 fprintf(debugFP, "\n");
947 return OutputToProcess(pr, message, count, outError);
950 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
957 if (appData.debugMode) {
958 fprintf(debugFP, ">ICS: ");
959 show_bytes(debugFP, buf, newcount);
960 fprintf(debugFP, "\n");
962 outcount = OutputToProcess(pr, buf, newcount, outError);
963 if (outcount < newcount) return -1; /* to be sure */
970 } else if (((unsigned char) *p) == TN_IAC) {
971 *q++ = (char) TN_IAC;
978 if (appData.debugMode) {
979 fprintf(debugFP, ">ICS: ");
980 show_bytes(debugFP, buf, newcount);
981 fprintf(debugFP, "\n");
983 outcount = OutputToProcess(pr, buf, newcount, outError);
984 if (outcount < newcount) return -1; /* to be sure */
989 read_from_player(isr, closure, message, count, error)
996 int outError, outCount;
997 static int gotEof = 0;
999 /* Pass data read from player on to ICS */
1002 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1003 if (outCount < count) {
1004 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1006 } else if (count < 0) {
1007 RemoveInputSource(isr);
1008 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1009 } else if (gotEof++ > 0) {
1010 RemoveInputSource(isr);
1011 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1019 int count, outCount, outError;
1021 if (icsPR == NULL) return;
1024 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1025 if (outCount < count) {
1026 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1030 /* This is used for sending logon scripts to the ICS. Sending
1031 without a delay causes problems when using timestamp on ICC
1032 (at least on my machine). */
1034 SendToICSDelayed(s,msdelay)
1038 int count, outCount, outError;
1040 if (icsPR == NULL) return;
1043 if (appData.debugMode) {
1044 fprintf(debugFP, ">ICS: ");
1045 show_bytes(debugFP, s, count);
1046 fprintf(debugFP, "\n");
1048 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1050 if (outCount < count) {
1051 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1056 /* Remove all highlighting escape sequences in s
1057 Also deletes any suffix starting with '('
1060 StripHighlightAndTitle(s)
1063 static char retbuf[MSG_SIZ];
1066 while (*s != NULLCHAR) {
1067 while (*s == '\033') {
1068 while (*s != NULLCHAR && !isalpha(*s)) s++;
1069 if (*s != NULLCHAR) s++;
1071 while (*s != NULLCHAR && *s != '\033') {
1072 if (*s == '(' || *s == '[') {
1083 /* Remove all highlighting escape sequences in s */
1088 static char retbuf[MSG_SIZ];
1091 while (*s != NULLCHAR) {
1092 while (*s == '\033') {
1093 while (*s != NULLCHAR && !isalpha(*s)) s++;
1094 if (*s != NULLCHAR) s++;
1096 while (*s != NULLCHAR && *s != '\033') {
1104 char *variantNames[] = VARIANT_NAMES;
1109 return variantNames[v];
1113 /* Identify a variant from the strings the chess servers use or the
1114 PGN Variant tag names we use. */
1121 VariantClass v = VariantNormal;
1122 int i, found = FALSE;
1127 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1128 if (StrCaseStr(e, variantNames[i])) {
1129 v = (VariantClass) i;
1136 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1137 || StrCaseStr(e, "wild/fr")) {
1138 v = VariantFischeRandom;
1139 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1140 (i = 1, p = StrCaseStr(e, "w"))) {
1142 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1149 case 0: /* FICS only, actually */
1151 /* Castling legal even if K starts on d-file */
1152 v = VariantWildCastle;
1157 /* Castling illegal even if K & R happen to start in
1158 normal positions. */
1159 v = VariantNoCastle;
1172 /* Castling legal iff K & R start in normal positions */
1178 /* Special wilds for position setup; unclear what to do here */
1179 v = VariantLoadable;
1182 /* Bizarre ICC game */
1183 v = VariantTwoKings;
1186 v = VariantKriegspiel;
1192 v = VariantFischeRandom;
1195 v = VariantCrazyhouse;
1198 v = VariantBughouse;
1204 /* Not quite the same as FICS suicide! */
1205 v = VariantGiveaway;
1211 v = VariantShatranj;
1214 /* Temporary names for future ICC types. The name *will* change in
1215 the next xboard/WinBoard release after ICC defines it. */
1242 /* Found "wild" or "w" in the string but no number;
1243 must assume it's normal chess. */
1247 sprintf(buf, "Unknown wild type %d", wnum);
1248 DisplayError(buf, 0);
1254 if (appData.debugMode) {
1255 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1256 e, wnum, VariantName(v));
1261 static int leftover_start = 0, leftover_len = 0;
1262 char star_match[STAR_MATCH_N][MSG_SIZ];
1264 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1265 advance *index beyond it, and set leftover_start to the new value of
1266 *index; else return FALSE. If pattern contains the character '*', it
1267 matches any sequence of characters not containing '\r', '\n', or the
1268 character following the '*' (if any), and the matched sequence(s) are
1269 copied into star_match.
1272 looking_at(buf, index, pattern)
1277 char *bufp = &buf[*index], *patternp = pattern;
1279 char *matchp = star_match[0];
1282 if (*patternp == NULLCHAR) {
1283 *index = leftover_start = bufp - buf;
1287 if (*bufp == NULLCHAR) return FALSE;
1288 if (*patternp == '*') {
1289 if (*bufp == *(patternp + 1)) {
1291 matchp = star_match[++star_count];
1295 } else if (*bufp == '\n' || *bufp == '\r') {
1297 if (*patternp == NULLCHAR)
1302 *matchp++ = *bufp++;
1306 if (*patternp != *bufp) return FALSE;
1313 SendToPlayer(data, length)
1317 int error, outCount;
1318 outCount = OutputToProcess(NoProc, data, length, &error);
1319 if (outCount < length) {
1320 DisplayFatalError(_("Error writing to display"), error, 1);
1325 PackHolding(packed, holding)
1337 switch (runlength) {
1348 sprintf(q, "%d", runlength);
1360 /* Telnet protocol requests from the front end */
1362 TelnetRequest(ddww, option)
1363 unsigned char ddww, option;
1365 unsigned char msg[3];
1366 int outCount, outError;
1368 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1370 if (appData.debugMode) {
1371 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1387 sprintf(buf1, "%d", ddww);
1396 sprintf(buf2, "%d", option);
1399 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1404 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1413 if (!appData.icsActive) return;
1414 TelnetRequest(TN_DO, TN_ECHO);
1420 if (!appData.icsActive) return;
1421 TelnetRequest(TN_DONT, TN_ECHO);
1424 static int loggedOn = FALSE;
1426 /*-- Game start info cache: --*/
1428 char gs_kind[MSG_SIZ];
1429 static char player1Name[128] = "";
1430 static char player2Name[128] = "";
1431 static int player1Rating = -1;
1432 static int player2Rating = -1;
1433 /*----------------------------*/
1436 read_from_ics(isr, closure, data, count, error)
1443 #define BUF_SIZE 8192
1444 #define STARTED_NONE 0
1445 #define STARTED_MOVES 1
1446 #define STARTED_BOARD 2
1447 #define STARTED_OBSERVE 3
1448 #define STARTED_HOLDINGS 4
1449 #define STARTED_CHATTER 5
1450 #define STARTED_COMMENT 6
1451 #define STARTED_MOVES_NOHIDE 7
1453 static int started = STARTED_NONE;
1454 static char parse[20000];
1455 static int parse_pos = 0;
1456 static char buf[BUF_SIZE + 1];
1457 static int firstTime = TRUE, intfSet = FALSE;
1458 static ColorClass curColor = ColorNormal;
1459 static ColorClass prevColor = ColorNormal;
1460 static int savingComment = FALSE;
1469 if (appData.debugMode) {
1471 fprintf(debugFP, "<ICS: ");
1472 show_bytes(debugFP, data, count);
1473 fprintf(debugFP, "\n");
1479 /* If last read ended with a partial line that we couldn't parse,
1480 prepend it to the new read and try again. */
1481 if (leftover_len > 0) {
1482 for (i=0; i<leftover_len; i++)
1483 buf[i] = buf[leftover_start + i];
1486 /* Copy in new characters, removing nulls and \r's */
1487 buf_len = leftover_len;
1488 for (i = 0; i < count; i++) {
1489 if (data[i] != NULLCHAR && data[i] != '\r')
1490 buf[buf_len++] = data[i];
1493 buf[buf_len] = NULLCHAR;
1494 next_out = leftover_len;
1498 while (i < buf_len) {
1499 /* Deal with part of the TELNET option negotiation
1500 protocol. We refuse to do anything beyond the
1501 defaults, except that we allow the WILL ECHO option,
1502 which ICS uses to turn off password echoing when we are
1503 directly connected to it. We reject this option
1504 if localLineEditing mode is on (always on in xboard)
1505 and we are talking to port 23, which might be a real
1506 telnet server that will try to keep WILL ECHO on permanently.
1508 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1509 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1510 unsigned char option;
1512 switch ((unsigned char) buf[++i]) {
1514 if (appData.debugMode)
1515 fprintf(debugFP, "\n<WILL ");
1516 switch (option = (unsigned char) buf[++i]) {
1518 if (appData.debugMode)
1519 fprintf(debugFP, "ECHO ");
1520 /* Reply only if this is a change, according
1521 to the protocol rules. */
1522 if (remoteEchoOption) break;
1523 if (appData.localLineEditing &&
1524 atoi(appData.icsPort) == TN_PORT) {
1525 TelnetRequest(TN_DONT, TN_ECHO);
1528 TelnetRequest(TN_DO, TN_ECHO);
1529 remoteEchoOption = TRUE;
1533 if (appData.debugMode)
1534 fprintf(debugFP, "%d ", option);
1535 /* Whatever this is, we don't want it. */
1536 TelnetRequest(TN_DONT, option);
1541 if (appData.debugMode)
1542 fprintf(debugFP, "\n<WONT ");
1543 switch (option = (unsigned char) buf[++i]) {
1545 if (appData.debugMode)
1546 fprintf(debugFP, "ECHO ");
1547 /* Reply only if this is a change, according
1548 to the protocol rules. */
1549 if (!remoteEchoOption) break;
1551 TelnetRequest(TN_DONT, TN_ECHO);
1552 remoteEchoOption = FALSE;
1555 if (appData.debugMode)
1556 fprintf(debugFP, "%d ", (unsigned char) option);
1557 /* Whatever this is, it must already be turned
1558 off, because we never agree to turn on
1559 anything non-default, so according to the
1560 protocol rules, we don't reply. */
1565 if (appData.debugMode)
1566 fprintf(debugFP, "\n<DO ");
1567 switch (option = (unsigned char) buf[++i]) {
1569 /* Whatever this is, we refuse to do it. */
1570 if (appData.debugMode)
1571 fprintf(debugFP, "%d ", option);
1572 TelnetRequest(TN_WONT, option);
1577 if (appData.debugMode)
1578 fprintf(debugFP, "\n<DONT ");
1579 switch (option = (unsigned char) buf[++i]) {
1581 if (appData.debugMode)
1582 fprintf(debugFP, "%d ", option);
1583 /* Whatever this is, we are already not doing
1584 it, because we never agree to do anything
1585 non-default, so according to the protocol
1586 rules, we don't reply. */
1591 if (appData.debugMode)
1592 fprintf(debugFP, "\n<IAC ");
1593 /* Doubled IAC; pass it through */
1597 if (appData.debugMode)
1598 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1599 /* Drop all other telnet commands on the floor */
1602 if (oldi > next_out)
1603 SendToPlayer(&buf[next_out], oldi - next_out);
1609 /* OK, this at least will *usually* work */
1610 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1614 if (loggedOn && !intfSet) {
1615 if (ics_type == ICS_ICC) {
1617 "/set-quietly interface %s\n/set-quietly style 12\n",
1620 } else if (ics_type == ICS_CHESSNET) {
1621 sprintf(str, "/style 12\n");
1623 strcpy(str, "alias $ @\n$set interface ");
1624 strcat(str, programVersion);
1625 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1627 strcat(str, "$iset nohighlight 1\n");
1629 strcat(str, "$iset lock 1\n$style 12\n");
1635 if (started == STARTED_COMMENT) {
1636 /* Accumulate characters in comment */
1637 parse[parse_pos++] = buf[i];
1638 if (buf[i] == '\n') {
1639 parse[parse_pos] = NULLCHAR;
1640 AppendComment(forwardMostMove, StripHighlight(parse));
1641 started = STARTED_NONE;
1643 /* Don't match patterns against characters in chatter */
1648 if (started == STARTED_CHATTER) {
1649 if (buf[i] != '\n') {
1650 /* Don't match patterns against characters in chatter */
1654 started = STARTED_NONE;
1657 /* Kludge to deal with rcmd protocol */
1658 if (firstTime && looking_at(buf, &i, "\001*")) {
1659 DisplayFatalError(&buf[1], 0, 1);
1665 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1668 if (appData.debugMode)
1669 fprintf(debugFP, "ics_type %d\n", ics_type);
1672 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1673 ics_type = ICS_FICS;
1675 if (appData.debugMode)
1676 fprintf(debugFP, "ics_type %d\n", ics_type);
1679 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1680 ics_type = ICS_CHESSNET;
1682 if (appData.debugMode)
1683 fprintf(debugFP, "ics_type %d\n", ics_type);
1688 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1689 looking_at(buf, &i, "Logging you in as \"*\"") ||
1690 looking_at(buf, &i, "will be \"*\""))) {
1691 strcpy(ics_handle, star_match[0]);
1695 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1697 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1698 DisplayIcsInteractionTitle(buf);
1699 have_set_title = TRUE;
1702 /* skip finger notes */
1703 if (started == STARTED_NONE &&
1704 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1705 (buf[i] == '1' && buf[i+1] == '0')) &&
1706 buf[i+2] == ':' && buf[i+3] == ' ') {
1707 started = STARTED_CHATTER;
1712 /* skip formula vars */
1713 if (started == STARTED_NONE &&
1714 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1715 started = STARTED_CHATTER;
1721 if (appData.zippyTalk || appData.zippyPlay) {
1723 if (ZippyControl(buf, &i) ||
1724 ZippyConverse(buf, &i) ||
1725 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1731 if (/* Don't color "message" or "messages" output */
1732 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1733 looking_at(buf, &i, "*. * at *:*: ") ||
1734 looking_at(buf, &i, "--* (*:*): ") ||
1735 /* Regular tells and says */
1736 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1737 looking_at(buf, &i, "* (your partner) tells you: ") ||
1738 looking_at(buf, &i, "* says: ") ||
1739 /* Message notifications (same color as tells) */
1740 looking_at(buf, &i, "* has left a message ") ||
1741 looking_at(buf, &i, "* just sent you a message:\n") ||
1742 /* Whispers and kibitzes */
1743 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1744 looking_at(buf, &i, "* kibitzes: ") ||
1746 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1748 if (tkind == 1 && strchr(star_match[0], ':')) {
1749 /* Avoid "tells you:" spoofs in channels */
1752 if (star_match[0][0] == NULLCHAR ||
1753 strchr(star_match[0], ' ') ||
1754 (tkind == 3 && strchr(star_match[1], ' '))) {
1755 /* Reject bogus matches */
1758 if (appData.colorize) {
1759 if (oldi > next_out) {
1760 SendToPlayer(&buf[next_out], oldi - next_out);
1765 Colorize(ColorTell, FALSE);
1766 curColor = ColorTell;
1769 Colorize(ColorKibitz, FALSE);
1770 curColor = ColorKibitz;
1773 p = strrchr(star_match[1], '(');
1780 Colorize(ColorChannel1, FALSE);
1781 curColor = ColorChannel1;
1783 Colorize(ColorChannel, FALSE);
1784 curColor = ColorChannel;
1788 curColor = ColorNormal;
1792 if (started == STARTED_NONE && appData.autoComment &&
1793 (gameMode == IcsObserving ||
1794 gameMode == IcsPlayingWhite ||
1795 gameMode == IcsPlayingBlack)) {
1796 parse_pos = i - oldi;
1797 memcpy(parse, &buf[oldi], parse_pos);
1798 parse[parse_pos] = NULLCHAR;
1799 started = STARTED_COMMENT;
1800 savingComment = TRUE;
1802 started = STARTED_CHATTER;
1803 savingComment = FALSE;
1810 if (looking_at(buf, &i, "* s-shouts: ") ||
1811 looking_at(buf, &i, "* c-shouts: ")) {
1812 if (appData.colorize) {
1813 if (oldi > next_out) {
1814 SendToPlayer(&buf[next_out], oldi - next_out);
1817 Colorize(ColorSShout, FALSE);
1818 curColor = ColorSShout;
1821 started = STARTED_CHATTER;
1825 if (looking_at(buf, &i, "--->")) {
1830 if (looking_at(buf, &i, "* shouts: ") ||
1831 looking_at(buf, &i, "--> ")) {
1832 if (appData.colorize) {
1833 if (oldi > next_out) {
1834 SendToPlayer(&buf[next_out], oldi - next_out);
1837 Colorize(ColorShout, FALSE);
1838 curColor = ColorShout;
1841 started = STARTED_CHATTER;
1845 if (looking_at( buf, &i, "Challenge:")) {
1846 if (appData.colorize) {
1847 if (oldi > next_out) {
1848 SendToPlayer(&buf[next_out], oldi - next_out);
1851 Colorize(ColorChallenge, FALSE);
1852 curColor = ColorChallenge;
1858 if (looking_at(buf, &i, "* offers you") ||
1859 looking_at(buf, &i, "* offers to be") ||
1860 looking_at(buf, &i, "* would like to") ||
1861 looking_at(buf, &i, "* requests to") ||
1862 looking_at(buf, &i, "Your opponent offers") ||
1863 looking_at(buf, &i, "Your opponent requests")) {
1865 if (appData.colorize) {
1866 if (oldi > next_out) {
1867 SendToPlayer(&buf[next_out], oldi - next_out);
1870 Colorize(ColorRequest, FALSE);
1871 curColor = ColorRequest;
1876 if (looking_at(buf, &i, "* (*) seeking")) {
1877 if (appData.colorize) {
1878 if (oldi > next_out) {
1879 SendToPlayer(&buf[next_out], oldi - next_out);
1882 Colorize(ColorSeek, FALSE);
1883 curColor = ColorSeek;
1889 if (looking_at(buf, &i, "\\ ")) {
1890 if (prevColor != ColorNormal) {
1891 if (oldi > next_out) {
1892 SendToPlayer(&buf[next_out], oldi - next_out);
1895 Colorize(prevColor, TRUE);
1896 curColor = prevColor;
1898 if (savingComment) {
1899 parse_pos = i - oldi;
1900 memcpy(parse, &buf[oldi], parse_pos);
1901 parse[parse_pos] = NULLCHAR;
1902 started = STARTED_COMMENT;
1904 started = STARTED_CHATTER;
1909 if (looking_at(buf, &i, "Black Strength :") ||
1910 looking_at(buf, &i, "<<< style 10 board >>>") ||
1911 looking_at(buf, &i, "<10>") ||
1912 looking_at(buf, &i, "#@#")) {
1913 /* Wrong board style */
1915 SendToICS(ics_prefix);
1916 SendToICS("set style 12\n");
1917 SendToICS(ics_prefix);
1918 SendToICS("refresh\n");
1922 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1924 have_sent_ICS_logon = 1;
1928 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1929 (looking_at(buf, &i, "\n<12> ") ||
1930 looking_at(buf, &i, "<12> "))) {
1932 if (oldi > next_out) {
1933 SendToPlayer(&buf[next_out], oldi - next_out);
1936 started = STARTED_BOARD;
1941 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1942 looking_at(buf, &i, "<b1> ")) {
1943 if (oldi > next_out) {
1944 SendToPlayer(&buf[next_out], oldi - next_out);
1947 started = STARTED_HOLDINGS;
1952 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1954 /* Header for a move list -- first line */
1956 switch (ics_getting_history) {
1960 case BeginningOfGame:
1961 /* User typed "moves" or "oldmoves" while we
1962 were idle. Pretend we asked for these
1963 moves and soak them up so user can step
1964 through them and/or save them.
1967 gameMode = IcsObserving;
1970 ics_getting_history = H_GOT_UNREQ_HEADER;
1972 case EditGame: /*?*/
1973 case EditPosition: /*?*/
1974 /* Should above feature work in these modes too? */
1975 /* For now it doesn't */
1976 ics_getting_history = H_GOT_UNWANTED_HEADER;
1979 ics_getting_history = H_GOT_UNWANTED_HEADER;
1984 /* Is this the right one? */
1985 if (gameInfo.white && gameInfo.black &&
1986 strcmp(gameInfo.white, star_match[0]) == 0 &&
1987 strcmp(gameInfo.black, star_match[2]) == 0) {
1989 ics_getting_history = H_GOT_REQ_HEADER;
1992 case H_GOT_REQ_HEADER:
1993 case H_GOT_UNREQ_HEADER:
1994 case H_GOT_UNWANTED_HEADER:
1995 case H_GETTING_MOVES:
1996 /* Should not happen */
1997 DisplayError(_("Error gathering move list: two headers"), 0);
1998 ics_getting_history = H_FALSE;
2002 /* Save player ratings into gameInfo if needed */
2003 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2004 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2005 (gameInfo.whiteRating == -1 ||
2006 gameInfo.blackRating == -1)) {
2008 gameInfo.whiteRating = string_to_rating(star_match[1]);
2009 gameInfo.blackRating = string_to_rating(star_match[3]);
2010 if (appData.debugMode)
2011 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2012 gameInfo.whiteRating, gameInfo.blackRating);
2017 if (looking_at(buf, &i,
2018 "* * match, initial time: * minute*, increment: * second")) {
2019 /* Header for a move list -- second line */
2020 /* Initial board will follow if this is a wild game */
2022 if (gameInfo.event != NULL) free(gameInfo.event);
2023 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2024 gameInfo.event = StrSave(str);
2025 gameInfo.variant = StringToVariant(gameInfo.event);
2029 if (looking_at(buf, &i, "Move ")) {
2030 /* Beginning of a move list */
2031 switch (ics_getting_history) {
2033 /* Normally should not happen */
2034 /* Maybe user hit reset while we were parsing */
2037 /* Happens if we are ignoring a move list that is not
2038 * the one we just requested. Common if the user
2039 * tries to observe two games without turning off
2042 case H_GETTING_MOVES:
2043 /* Should not happen */
2044 DisplayError(_("Error gathering move list: nested"), 0);
2045 ics_getting_history = H_FALSE;
2047 case H_GOT_REQ_HEADER:
2048 ics_getting_history = H_GETTING_MOVES;
2049 started = STARTED_MOVES;
2051 if (oldi > next_out) {
2052 SendToPlayer(&buf[next_out], oldi - next_out);
2055 case H_GOT_UNREQ_HEADER:
2056 ics_getting_history = H_GETTING_MOVES;
2057 started = STARTED_MOVES_NOHIDE;
2060 case H_GOT_UNWANTED_HEADER:
2061 ics_getting_history = H_FALSE;
2067 if (looking_at(buf, &i, "% ") ||
2068 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2069 && looking_at(buf, &i, "}*"))) {
2070 savingComment = FALSE;
2073 case STARTED_MOVES_NOHIDE:
2074 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2075 parse[parse_pos + i - oldi] = NULLCHAR;
2076 ParseGameHistory(parse);
2078 if (appData.zippyPlay && first.initDone) {
2079 FeedMovesToProgram(&first, forwardMostMove);
2080 if (gameMode == IcsPlayingWhite) {
2081 if (WhiteOnMove(forwardMostMove)) {
2082 if (first.sendTime) {
2083 if (first.useColors) {
2084 SendToProgram("black\n", &first);
2086 SendTimeRemaining(&first, TRUE);
2088 if (first.useColors) {
2089 SendToProgram("white\ngo\n", &first);
2091 SendToProgram("go\n", &first);
2093 first.maybeThinking = TRUE;
2095 if (first.usePlayother) {
2096 if (first.sendTime) {
2097 SendTimeRemaining(&first, TRUE);
2099 SendToProgram("playother\n", &first);
2105 } else if (gameMode == IcsPlayingBlack) {
2106 if (!WhiteOnMove(forwardMostMove)) {
2107 if (first.sendTime) {
2108 if (first.useColors) {
2109 SendToProgram("white\n", &first);
2111 SendTimeRemaining(&first, FALSE);
2113 if (first.useColors) {
2114 SendToProgram("black\ngo\n", &first);
2116 SendToProgram("go\n", &first);
2118 first.maybeThinking = TRUE;
2120 if (first.usePlayother) {
2121 if (first.sendTime) {
2122 SendTimeRemaining(&first, FALSE);
2124 SendToProgram("playother\n", &first);
2133 if (gameMode == IcsObserving && ics_gamenum == -1) {
2134 /* Moves came from oldmoves or moves command
2135 while we weren't doing anything else.
2137 currentMove = forwardMostMove;
2138 ClearHighlights();/*!!could figure this out*/
2139 flipView = appData.flipView;
2140 DrawPosition(FALSE, boards[currentMove]);
2141 DisplayBothClocks();
2142 sprintf(str, "%s vs. %s",
2143 gameInfo.white, gameInfo.black);
2147 /* Moves were history of an active game */
2148 if (gameInfo.resultDetails != NULL) {
2149 free(gameInfo.resultDetails);
2150 gameInfo.resultDetails = NULL;
2153 HistorySet(parseList, backwardMostMove,
2154 forwardMostMove, currentMove-1);
2155 DisplayMove(currentMove - 1);
2156 if (started == STARTED_MOVES) next_out = i;
2157 started = STARTED_NONE;
2158 ics_getting_history = H_FALSE;
2161 case STARTED_OBSERVE:
2162 started = STARTED_NONE;
2163 SendToICS(ics_prefix);
2164 SendToICS("refresh\n");
2173 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2174 started == STARTED_HOLDINGS ||
2175 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2176 /* Accumulate characters in move list or board */
2177 parse[parse_pos++] = buf[i];
2180 /* Start of game messages. Mostly we detect start of game
2181 when the first board image arrives. On some versions
2182 of the ICS, though, we need to do a "refresh" after starting
2183 to observe in order to get the current board right away. */
2184 if (looking_at(buf, &i, "Adding game * to observation list")) {
2185 started = STARTED_OBSERVE;
2189 /* Handle auto-observe */
2190 if (appData.autoObserve &&
2191 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2192 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2194 /* Choose the player that was highlighted, if any. */
2195 if (star_match[0][0] == '\033' ||
2196 star_match[1][0] != '\033') {
2197 player = star_match[0];
2199 player = star_match[2];
2201 sprintf(str, "%sobserve %s\n",
2202 ics_prefix, StripHighlightAndTitle(player));
2205 /* Save ratings from notify string */
2206 strcpy(player1Name, star_match[0]);
2207 player1Rating = string_to_rating(star_match[1]);
2208 strcpy(player2Name, star_match[2]);
2209 player2Rating = string_to_rating(star_match[3]);
2211 if (appData.debugMode)
2213 "Ratings from 'Game notification:' %s %d, %s %d\n",
2214 player1Name, player1Rating,
2215 player2Name, player2Rating);
2220 /* Deal with automatic examine mode after a game,
2221 and with IcsObserving -> IcsExamining transition */
2222 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2223 looking_at(buf, &i, "has made you an examiner of game *")) {
2225 int gamenum = atoi(star_match[0]);
2226 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2227 gamenum == ics_gamenum) {
2228 /* We were already playing or observing this game;
2229 no need to refetch history */
2230 gameMode = IcsExamining;
2232 pauseExamForwardMostMove = forwardMostMove;
2233 } else if (currentMove < forwardMostMove) {
2234 ForwardInner(forwardMostMove);
2237 /* I don't think this case really can happen */
2238 SendToICS(ics_prefix);
2239 SendToICS("refresh\n");
2244 /* Error messages */
2245 if (ics_user_moved) {
2246 if (looking_at(buf, &i, "Illegal move") ||
2247 looking_at(buf, &i, "Not a legal move") ||
2248 looking_at(buf, &i, "Your king is in check") ||
2249 looking_at(buf, &i, "It isn't your turn") ||
2250 looking_at(buf, &i, "It is not your move")) {
2253 if (forwardMostMove > backwardMostMove) {
2254 currentMove = --forwardMostMove;
2255 DisplayMove(currentMove - 1); /* before DMError */
2256 DisplayMoveError("Illegal move (rejected by ICS)");
2257 DrawPosition(FALSE, boards[currentMove]);
2259 DisplayBothClocks();
2265 if (looking_at(buf, &i, "still have time") ||
2266 looking_at(buf, &i, "not out of time") ||
2267 looking_at(buf, &i, "either player is out of time") ||
2268 looking_at(buf, &i, "has timeseal; checking")) {
2269 /* We must have called his flag a little too soon */
2270 whiteFlag = blackFlag = FALSE;
2274 if (looking_at(buf, &i, "added * seconds to") ||
2275 looking_at(buf, &i, "seconds were added to")) {
2276 /* Update the clocks */
2277 SendToICS(ics_prefix);
2278 SendToICS("refresh\n");
2282 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2283 ics_clock_paused = TRUE;
2288 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2289 ics_clock_paused = FALSE;
2294 /* Grab player ratings from the Creating: message.
2295 Note we have to check for the special case when
2296 the ICS inserts things like [white] or [black]. */
2297 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2298 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2300 0 player 1 name (not necessarily white)
2302 2 empty, white, or black (IGNORED)
2303 3 player 2 name (not necessarily black)
2306 The names/ratings are sorted out when the game
2307 actually starts (below).
2309 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2310 player1Rating = string_to_rating(star_match[1]);
2311 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2312 player2Rating = string_to_rating(star_match[4]);
2314 if (appData.debugMode)
2316 "Ratings from 'Creating:' %s %d, %s %d\n",
2317 player1Name, player1Rating,
2318 player2Name, player2Rating);
2323 /* Improved generic start/end-of-game messages */
2324 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2325 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2326 /* If tkind == 0: */
2327 /* star_match[0] is the game number */
2328 /* [1] is the white player's name */
2329 /* [2] is the black player's name */
2330 /* For end-of-game: */
2331 /* [3] is the reason for the game end */
2332 /* [4] is a PGN end game-token, preceded by " " */
2333 /* For start-of-game: */
2334 /* [3] begins with "Creating" or "Continuing" */
2335 /* [4] is " *" or empty (don't care). */
2336 int gamenum = atoi(star_match[0]);
2337 char *whitename, *blackname, *why, *endtoken;
2338 ChessMove endtype = (ChessMove) 0;
2341 whitename = star_match[1];
2342 blackname = star_match[2];
2343 why = star_match[3];
2344 endtoken = star_match[4];
2346 whitename = star_match[1];
2347 blackname = star_match[3];
2348 why = star_match[5];
2349 endtoken = star_match[6];
2352 /* Game start messages */
2353 if (strncmp(why, "Creating ", 9) == 0 ||
2354 strncmp(why, "Continuing ", 11) == 0) {
2355 gs_gamenum = gamenum;
2356 strcpy(gs_kind, strchr(why, ' ') + 1);
2358 if (appData.zippyPlay) {
2359 ZippyGameStart(whitename, blackname);
2365 /* Game end messages */
2366 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2367 ics_gamenum != gamenum) {
2370 while (endtoken[0] == ' ') endtoken++;
2371 switch (endtoken[0]) {
2374 endtype = GameUnfinished;
2377 endtype = BlackWins;
2380 if (endtoken[1] == '/')
2381 endtype = GameIsDrawn;
2383 endtype = WhiteWins;
2386 GameEnds(endtype, why, GE_ICS);
2388 if (appData.zippyPlay && first.initDone) {
2389 ZippyGameEnd(endtype, why);
2390 if (first.pr == NULL) {
2391 /* Start the next process early so that we'll
2392 be ready for the next challenge */
2393 StartChessProgram(&first);
2395 /* Send "new" early, in case this command takes
2396 a long time to finish, so that we'll be ready
2397 for the next challenge. */
2404 if (looking_at(buf, &i, "Removing game * from observation") ||
2405 looking_at(buf, &i, "no longer observing game *") ||
2406 looking_at(buf, &i, "Game * (*) has no examiners")) {
2407 if (gameMode == IcsObserving &&
2408 atoi(star_match[0]) == ics_gamenum)
2413 ics_user_moved = FALSE;
2418 if (looking_at(buf, &i, "no longer examining game *")) {
2419 if (gameMode == IcsExamining &&
2420 atoi(star_match[0]) == ics_gamenum)
2424 ics_user_moved = FALSE;
2429 /* Advance leftover_start past any newlines we find,
2430 so only partial lines can get reparsed */
2431 if (looking_at(buf, &i, "\n")) {
2432 prevColor = curColor;
2433 if (curColor != ColorNormal) {
2434 if (oldi > next_out) {
2435 SendToPlayer(&buf[next_out], oldi - next_out);
2438 Colorize(ColorNormal, FALSE);
2439 curColor = ColorNormal;
2441 if (started == STARTED_BOARD) {
2442 started = STARTED_NONE;
2443 parse[parse_pos] = NULLCHAR;
2444 ParseBoard12(parse);
2447 /* Send premove here */
2448 if (appData.premove) {
2450 if (currentMove == 0 &&
2451 gameMode == IcsPlayingWhite &&
2452 appData.premoveWhite) {
2453 sprintf(str, "%s%s\n", ics_prefix,
2454 appData.premoveWhiteText);
2455 if (appData.debugMode)
2456 fprintf(debugFP, "Sending premove:\n");
2458 } else if (currentMove == 1 &&
2459 gameMode == IcsPlayingBlack &&
2460 appData.premoveBlack) {
2461 sprintf(str, "%s%s\n", ics_prefix,
2462 appData.premoveBlackText);
2463 if (appData.debugMode)
2464 fprintf(debugFP, "Sending premove:\n");
2466 } else if (gotPremove) {
2468 ClearPremoveHighlights();
2469 if (appData.debugMode)
2470 fprintf(debugFP, "Sending premove:\n");
2471 UserMoveEvent(premoveFromX, premoveFromY,
2472 premoveToX, premoveToY,
2477 /* Usually suppress following prompt */
2478 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2479 if (looking_at(buf, &i, "*% ")) {
2480 savingComment = FALSE;
2484 } else if (started == STARTED_HOLDINGS) {
2486 char new_piece[MSG_SIZ];
2487 started = STARTED_NONE;
2488 parse[parse_pos] = NULLCHAR;
2489 if (appData.debugMode)
2490 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2491 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2492 gamenum == ics_gamenum) {
2493 if (gameInfo.variant == VariantNormal) {
2494 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2495 /* Get a move list just to see the header, which
2496 will tell us whether this is really bug or zh */
2497 if (ics_getting_history == H_FALSE) {
2498 ics_getting_history = H_REQUESTED;
2499 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2503 new_piece[0] = NULLCHAR;
2504 sscanf(parse, "game %d white [%s black [%s <- %s",
2505 &gamenum, white_holding, black_holding,
2507 white_holding[strlen(white_holding)-1] = NULLCHAR;
2508 black_holding[strlen(black_holding)-1] = NULLCHAR;
2510 if (appData.zippyPlay && first.initDone) {
2511 ZippyHoldings(white_holding, black_holding,
2515 if (tinyLayout || smallLayout) {
2516 char wh[16], bh[16];
2517 PackHolding(wh, white_holding);
2518 PackHolding(bh, black_holding);
2519 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2520 gameInfo.white, gameInfo.black);
2522 sprintf(str, "%s [%s] vs. %s [%s]",
2523 gameInfo.white, white_holding,
2524 gameInfo.black, black_holding);
2526 DrawPosition(FALSE, NULL);
2529 /* Suppress following prompt */
2530 if (looking_at(buf, &i, "*% ")) {
2531 savingComment = FALSE;
2538 i++; /* skip unparsed character and loop back */
2541 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2542 started != STARTED_HOLDINGS && i > next_out) {
2543 SendToPlayer(&buf[next_out], i - next_out);
2547 leftover_len = buf_len - leftover_start;
2548 /* if buffer ends with something we couldn't parse,
2549 reparse it after appending the next read */
2551 } else if (count == 0) {
2552 RemoveInputSource(isr);
2553 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2555 DisplayFatalError(_("Error reading from ICS"), error, 1);
2560 /* Board style 12 looks like this:
2562 <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
2564 * The "<12> " is stripped before it gets to this routine. The two
2565 * trailing 0's (flip state and clock ticking) are later addition, and
2566 * some chess servers may not have them, or may have only the first.
2567 * Additional trailing fields may be added in the future.
2570 #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"
2572 #define RELATION_OBSERVING_PLAYED 0
2573 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2574 #define RELATION_PLAYING_MYMOVE 1
2575 #define RELATION_PLAYING_NOTMYMOVE -1
2576 #define RELATION_EXAMINING 2
2577 #define RELATION_ISOLATED_BOARD -3
2578 #define RELATION_STARTING_POSITION -4 /* FICS only */
2581 ParseBoard12(string)
2584 GameMode newGameMode;
2585 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2586 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2587 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2588 char to_play, board_chars[72];
2589 char move_str[500], str[500], elapsed_time[500];
2590 char black[32], white[32];
2592 int prevMove = currentMove;
2595 int fromX, fromY, toX, toY;
2598 fromX = fromY = toX = toY = -1;
2602 if (appData.debugMode)
2603 fprintf(debugFP, _("Parsing board: %s\n"), string);
2605 move_str[0] = NULLCHAR;
2606 elapsed_time[0] = NULLCHAR;
2607 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2608 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2609 &gamenum, white, black, &relation, &basetime, &increment,
2610 &white_stren, &black_stren, &white_time, &black_time,
2611 &moveNum, str, elapsed_time, move_str, &ics_flip,
2615 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2616 DisplayError(str, 0);
2620 /* Convert the move number to internal form */
2621 moveNum = (moveNum - 1) * 2;
2622 if (to_play == 'B') moveNum++;
2623 if (moveNum >= MAX_MOVES) {
2624 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2630 case RELATION_OBSERVING_PLAYED:
2631 case RELATION_OBSERVING_STATIC:
2632 if (gamenum == -1) {
2633 /* Old ICC buglet */
2634 relation = RELATION_OBSERVING_STATIC;
2636 newGameMode = IcsObserving;
2638 case RELATION_PLAYING_MYMOVE:
2639 case RELATION_PLAYING_NOTMYMOVE:
2641 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2642 IcsPlayingWhite : IcsPlayingBlack;
2644 case RELATION_EXAMINING:
2645 newGameMode = IcsExamining;
2647 case RELATION_ISOLATED_BOARD:
2649 /* Just display this board. If user was doing something else,
2650 we will forget about it until the next board comes. */
2651 newGameMode = IcsIdle;
2653 case RELATION_STARTING_POSITION:
2654 newGameMode = gameMode;
2658 /* Modify behavior for initial board display on move listing
2661 switch (ics_getting_history) {
2665 case H_GOT_REQ_HEADER:
2666 case H_GOT_UNREQ_HEADER:
2667 /* This is the initial position of the current game */
2668 gamenum = ics_gamenum;
2669 moveNum = 0; /* old ICS bug workaround */
2670 if (to_play == 'B') {
2671 startedFromSetupPosition = TRUE;
2672 blackPlaysFirst = TRUE;
2674 if (forwardMostMove == 0) forwardMostMove = 1;
2675 if (backwardMostMove == 0) backwardMostMove = 1;
2676 if (currentMove == 0) currentMove = 1;
2678 newGameMode = gameMode;
2679 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2681 case H_GOT_UNWANTED_HEADER:
2682 /* This is an initial board that we don't want */
2684 case H_GETTING_MOVES:
2685 /* Should not happen */
2686 DisplayError(_("Error gathering move list: extra board"), 0);
2687 ics_getting_history = H_FALSE;
2691 /* Take action if this is the first board of a new game, or of a
2692 different game than is currently being displayed. */
2693 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2694 relation == RELATION_ISOLATED_BOARD) {
2696 /* Forget the old game and get the history (if any) of the new one */
2697 if (gameMode != BeginningOfGame) {
2701 if (appData.autoRaiseBoard) BoardToTop();
2703 if (gamenum == -1) {
2704 newGameMode = IcsIdle;
2705 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2706 appData.getMoveList) {
2707 /* Need to get game history */
2708 ics_getting_history = H_REQUESTED;
2709 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2713 /* Initially flip the board to have black on the bottom if playing
2714 black or if the ICS flip flag is set, but let the user change
2715 it with the Flip View button. */
2716 flipView = appData.autoFlipView ?
2717 (newGameMode == IcsPlayingBlack) || ics_flip :
2720 /* Done with values from previous mode; copy in new ones */
2721 gameMode = newGameMode;
2723 ics_gamenum = gamenum;
2724 if (gamenum == gs_gamenum) {
2725 int klen = strlen(gs_kind);
2726 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2727 sprintf(str, "ICS %s", gs_kind);
2728 gameInfo.event = StrSave(str);
2730 gameInfo.event = StrSave("ICS game");
2732 gameInfo.site = StrSave(appData.icsHost);
2733 gameInfo.date = PGNDate();
2734 gameInfo.round = StrSave("-");
2735 gameInfo.white = StrSave(white);
2736 gameInfo.black = StrSave(black);
2737 timeControl = basetime * 60 * 1000;
2738 timeIncrement = increment * 1000;
2739 movesPerSession = 0;
2740 gameInfo.timeControl = TimeControlTagValue();
2741 gameInfo.variant = StringToVariant(gameInfo.event);
2743 /* Do we have the ratings? */
2744 if (strcmp(player1Name, white) == 0 &&
2745 strcmp(player2Name, black) == 0) {
2746 if (appData.debugMode)
2747 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2748 player1Rating, player2Rating);
2749 gameInfo.whiteRating = player1Rating;
2750 gameInfo.blackRating = player2Rating;
2751 } else if (strcmp(player2Name, white) == 0 &&
2752 strcmp(player1Name, black) == 0) {
2753 if (appData.debugMode)
2754 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2755 player2Rating, player1Rating);
2756 gameInfo.whiteRating = player2Rating;
2757 gameInfo.blackRating = player1Rating;
2759 player1Name[0] = player2Name[0] = NULLCHAR;
2761 /* Silence shouts if requested */
2762 if (appData.quietPlay &&
2763 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2764 SendToICS(ics_prefix);
2765 SendToICS("set shout 0\n");
2769 /* Deal with midgame name changes */
2771 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2772 if (gameInfo.white) free(gameInfo.white);
2773 gameInfo.white = StrSave(white);
2775 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2776 if (gameInfo.black) free(gameInfo.black);
2777 gameInfo.black = StrSave(black);
2781 /* Throw away game result if anything actually changes in examine mode */
2782 if (gameMode == IcsExamining && !newGame) {
2783 gameInfo.result = GameUnfinished;
2784 if (gameInfo.resultDetails != NULL) {
2785 free(gameInfo.resultDetails);
2786 gameInfo.resultDetails = NULL;
2790 /* In pausing && IcsExamining mode, we ignore boards coming
2791 in if they are in a different variation than we are. */
2792 if (pauseExamInvalid) return;
2793 if (pausing && gameMode == IcsExamining) {
2794 if (moveNum <= pauseExamForwardMostMove) {
2795 pauseExamInvalid = TRUE;
2796 forwardMostMove = pauseExamForwardMostMove;
2801 /* Parse the board */
2802 for (k = 0; k < 8; k++)
2803 for (j = 0; j < 8; j++)
2804 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2805 CopyBoard(boards[moveNum], board);
2807 startedFromSetupPosition =
2808 !CompareBoards(board, initialPosition);
2811 if (ics_getting_history == H_GOT_REQ_HEADER ||
2812 ics_getting_history == H_GOT_UNREQ_HEADER) {
2813 /* This was an initial position from a move list, not
2814 the current position */
2818 /* Update currentMove and known move number limits */
2819 newMove = newGame || moveNum > forwardMostMove;
2821 forwardMostMove = backwardMostMove = currentMove = moveNum;
2822 if (gameMode == IcsExamining && moveNum == 0) {
2823 /* Workaround for ICS limitation: we are not told the wild
2824 type when starting to examine a game. But if we ask for
2825 the move list, the move list header will tell us */
2826 ics_getting_history = H_REQUESTED;
2827 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2830 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2831 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2832 forwardMostMove = moveNum;
2833 if (!pausing || currentMove > forwardMostMove)
2834 currentMove = forwardMostMove;
2836 /* New part of history that is not contiguous with old part */
2837 if (pausing && gameMode == IcsExamining) {
2838 pauseExamInvalid = TRUE;
2839 forwardMostMove = pauseExamForwardMostMove;
2842 forwardMostMove = backwardMostMove = currentMove = moveNum;
2843 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2844 ics_getting_history = H_REQUESTED;
2845 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2850 /* Update the clocks */
2851 if (strchr(elapsed_time, '.')) {
2853 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2854 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2856 /* Time is in seconds */
2857 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2858 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2863 if (appData.zippyPlay && newGame &&
2864 gameMode != IcsObserving && gameMode != IcsIdle &&
2865 gameMode != IcsExamining)
2866 ZippyFirstBoard(moveNum, basetime, increment);
2869 /* Put the move on the move list, first converting
2870 to canonical algebraic form. */
2872 if (moveNum <= backwardMostMove) {
2873 /* We don't know what the board looked like before
2875 strcpy(parseList[moveNum - 1], move_str);
2876 strcat(parseList[moveNum - 1], " ");
2877 strcat(parseList[moveNum - 1], elapsed_time);
2878 moveList[moveNum - 1][0] = NULLCHAR;
2879 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2880 &fromX, &fromY, &toX, &toY, &promoChar)) {
2881 (void) CoordsToAlgebraic(boards[moveNum - 1],
2882 PosFlags(moveNum - 1), EP_UNKNOWN,
2883 fromY, fromX, toY, toX, promoChar,
2884 parseList[moveNum-1]);
2885 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2891 strcat(parseList[moveNum - 1], "+");
2894 strcat(parseList[moveNum - 1], "#");
2897 strcat(parseList[moveNum - 1], " ");
2898 strcat(parseList[moveNum - 1], elapsed_time);
2899 /* currentMoveString is set as a side-effect of ParseOneMove */
2900 strcpy(moveList[moveNum - 1], currentMoveString);
2901 strcat(moveList[moveNum - 1], "\n");
2902 } else if (strcmp(move_str, "none") == 0) {
2903 /* Again, we don't know what the board looked like;
2904 this is really the start of the game. */
2905 parseList[moveNum - 1][0] = NULLCHAR;
2906 moveList[moveNum - 1][0] = NULLCHAR;
2907 backwardMostMove = moveNum;
2908 startedFromSetupPosition = TRUE;
2909 fromX = fromY = toX = toY = -1;
2911 /* Move from ICS was illegal!? Punt. */
2913 if (appData.testLegality && appData.debugMode) {
2914 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2915 DisplayError(str, 0);
2918 strcpy(parseList[moveNum - 1], move_str);
2919 strcat(parseList[moveNum - 1], " ");
2920 strcat(parseList[moveNum - 1], elapsed_time);
2921 moveList[moveNum - 1][0] = NULLCHAR;
2922 fromX = fromY = toX = toY = -1;
2926 /* Send move to chess program (BEFORE animating it). */
2927 if (appData.zippyPlay && !newGame && newMove &&
2928 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2930 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2931 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2932 if (moveList[moveNum - 1][0] == NULLCHAR) {
2933 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2935 DisplayError(str, 0);
2937 if (first.sendTime) {
2938 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2940 SendMoveToProgram(moveNum - 1, &first);
2943 if (first.useColors) {
2944 SendToProgram(gameMode == IcsPlayingWhite ?
2946 "black\ngo\n", &first);
2948 SendToProgram("go\n", &first);
2950 first.maybeThinking = TRUE;
2953 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
2954 if (moveList[moveNum - 1][0] == NULLCHAR) {
2955 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
2956 DisplayError(str, 0);
2958 SendMoveToProgram(moveNum - 1, &first);
2965 if (moveNum > 0 && !gotPremove) {
2966 /* If move comes from a remote source, animate it. If it
2967 isn't remote, it will have already been animated. */
2968 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
2969 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
2971 if (!pausing && appData.highlightLastMove) {
2972 SetHighlights(fromX, fromY, toX, toY);
2976 /* Start the clocks */
2977 whiteFlag = blackFlag = FALSE;
2978 appData.clockMode = !(basetime == 0 && increment == 0);
2980 ics_clock_paused = TRUE;
2982 } else if (ticking == 1) {
2983 ics_clock_paused = FALSE;
2985 if (gameMode == IcsIdle ||
2986 relation == RELATION_OBSERVING_STATIC ||
2987 relation == RELATION_EXAMINING ||
2989 DisplayBothClocks();
2993 /* Display opponents and material strengths */
2994 if (gameInfo.variant != VariantBughouse &&
2995 gameInfo.variant != VariantCrazyhouse) {
2996 if (tinyLayout || smallLayout) {
2997 sprintf(str, "%s(%d) %s(%d) {%d %d}",
2998 gameInfo.white, white_stren, gameInfo.black, black_stren,
2999 basetime, increment);
3001 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3002 gameInfo.white, white_stren, gameInfo.black, black_stren,
3003 basetime, increment);
3009 /* Display the board */
3012 if (appData.premove)
3014 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3015 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3016 ClearPremoveHighlights();
3018 DrawPosition(FALSE, boards[currentMove]);
3019 DisplayMove(moveNum - 1);
3020 if (appData.ringBellAfterMoves && !ics_user_moved)
3024 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3031 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3032 ics_getting_history = H_REQUESTED;
3033 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3039 AnalysisPeriodicEvent(force)
3042 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3043 && !force) || !appData.periodicUpdates)
3046 /* Send . command to Crafty to collect stats */
3047 SendToProgram(".\n", &first);
3049 /* Don't send another until we get a response (this makes
3050 us stop sending to old Crafty's which don't understand
3051 the "." command (sending illegal cmds resets node count & time,
3052 which looks bad)) */
3053 programStats.ok_to_send = 0;
3057 SendMoveToProgram(moveNum, cps)
3059 ChessProgramState *cps;
3062 if (cps->useUsermove) {
3063 SendToProgram("usermove ", cps);
3067 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3068 int len = space - parseList[moveNum];
3069 memcpy(buf, parseList[moveNum], len);
3071 buf[len] = NULLCHAR;
3073 sprintf(buf, "%s\n", parseList[moveNum]);
3075 SendToProgram(buf, cps);
3077 SendToProgram(moveList[moveNum], cps);
3082 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3084 int fromX, fromY, toX, toY;
3086 char user_move[MSG_SIZ];
3090 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3091 (int)moveType, fromX, fromY, toX, toY);
3092 DisplayError(user_move + strlen("say "), 0);
3094 case WhiteKingSideCastle:
3095 case BlackKingSideCastle:
3096 case WhiteQueenSideCastleWild:
3097 case BlackQueenSideCastleWild:
3098 sprintf(user_move, "o-o\n");
3100 case WhiteQueenSideCastle:
3101 case BlackQueenSideCastle:
3102 case WhiteKingSideCastleWild:
3103 case BlackKingSideCastleWild:
3104 sprintf(user_move, "o-o-o\n");
3106 case WhitePromotionQueen:
3107 case BlackPromotionQueen:
3108 case WhitePromotionRook:
3109 case BlackPromotionRook:
3110 case WhitePromotionBishop:
3111 case BlackPromotionBishop:
3112 case WhitePromotionKnight:
3113 case BlackPromotionKnight:
3114 case WhitePromotionKing:
3115 case BlackPromotionKing:
3116 sprintf(user_move, "%c%c%c%c=%c\n",
3117 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3118 PieceToChar(PromoPiece(moveType)));
3122 sprintf(user_move, "%c@%c%c\n",
3123 ToUpper(PieceToChar((ChessSquare) fromX)),
3124 'a' + toX, '1' + toY);
3127 case WhiteCapturesEnPassant:
3128 case BlackCapturesEnPassant:
3129 case IllegalMove: /* could be a variant we don't quite understand */
3130 sprintf(user_move, "%c%c%c%c\n",
3131 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3134 SendToICS(user_move);
3138 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3143 if (rf == DROP_RANK) {
3144 sprintf(move, "%c@%c%c\n",
3145 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3147 if (promoChar == 'x' || promoChar == NULLCHAR) {
3148 sprintf(move, "%c%c%c%c\n",
3149 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3151 sprintf(move, "%c%c%c%c%c\n",
3152 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3158 ProcessICSInitScript(f)
3163 while (fgets(buf, MSG_SIZ, f)) {
3164 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3171 /* Parser for moves from gnuchess, ICS, or user typein box */
3173 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3176 ChessMove *moveType;
3177 int *fromX, *fromY, *toX, *toY;
3180 *moveType = yylexstr(moveNum, move);
3181 switch (*moveType) {
3182 case WhitePromotionQueen:
3183 case BlackPromotionQueen:
3184 case WhitePromotionRook:
3185 case BlackPromotionRook:
3186 case WhitePromotionBishop:
3187 case BlackPromotionBishop:
3188 case WhitePromotionKnight:
3189 case BlackPromotionKnight:
3190 case WhitePromotionKing:
3191 case BlackPromotionKing:
3193 case WhiteCapturesEnPassant:
3194 case BlackCapturesEnPassant:
3195 case WhiteKingSideCastle:
3196 case WhiteQueenSideCastle:
3197 case BlackKingSideCastle:
3198 case BlackQueenSideCastle:
3199 case WhiteKingSideCastleWild:
3200 case WhiteQueenSideCastleWild:
3201 case BlackKingSideCastleWild:
3202 case BlackQueenSideCastleWild:
3203 case IllegalMove: /* bug or odd chess variant */
3204 *fromX = currentMoveString[0] - 'a';
3205 *fromY = currentMoveString[1] - '1';
3206 *toX = currentMoveString[2] - 'a';
3207 *toY = currentMoveString[3] - '1';
3208 *promoChar = currentMoveString[4];
3209 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3210 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3211 *fromX = *fromY = *toX = *toY = 0;
3214 if (appData.testLegality) {
3215 return (*moveType != IllegalMove);
3217 return !(fromX == fromY && toX == toY);
3222 *fromX = *moveType == WhiteDrop ?
3223 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3224 (int) CharToPiece(ToLower(currentMoveString[0]));
3226 *toX = currentMoveString[2] - 'a';
3227 *toY = currentMoveString[3] - '1';
3228 *promoChar = NULLCHAR;
3232 case ImpossibleMove:
3233 case (ChessMove) 0: /* end of file */
3243 *fromX = *fromY = *toX = *toY = 0;
3244 *promoChar = NULLCHAR;
3251 InitPosition(redraw)
3254 currentMove = forwardMostMove = backwardMostMove = 0;
3255 switch (gameInfo.variant) {
3257 CopyBoard(boards[0], initialPosition);
3259 case VariantTwoKings:
3260 CopyBoard(boards[0], twoKingsPosition);
3261 startedFromSetupPosition = TRUE;
3263 case VariantWildCastle:
3264 CopyBoard(boards[0], initialPosition);
3265 /* !!?shuffle with kings guaranteed to be on d or e file */
3267 case VariantNoCastle:
3268 CopyBoard(boards[0], initialPosition);
3269 /* !!?unconstrained back-rank shuffle */
3271 case VariantFischeRandom:
3272 CopyBoard(boards[0], initialPosition);
3273 /* !!shuffle according to FR rules */
3277 DrawPosition(FALSE, boards[currentMove]);
3281 SendBoard(cps, moveNum)
3282 ChessProgramState *cps;
3285 char message[MSG_SIZ];
3287 if (cps->useSetboard) {
3288 char* fen = PositionToFEN(moveNum);
3289 sprintf(message, "setboard %s\n", fen);
3290 SendToProgram(message, cps);
3296 /* Kludge to set black to move, avoiding the troublesome and now
3297 * deprecated "black" command.
3299 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3301 SendToProgram("edit\n", cps);
3302 SendToProgram("#\n", cps);
3303 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3304 bp = &boards[moveNum][i][0];
3305 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3306 if ((int) *bp < (int) BlackPawn) {
3307 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3309 SendToProgram(message, cps);
3314 SendToProgram("c\n", cps);
3315 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3316 bp = &boards[moveNum][i][0];
3317 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3318 if (((int) *bp != (int) EmptySquare)
3319 && ((int) *bp >= (int) BlackPawn)) {
3320 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3322 SendToProgram(message, cps);
3327 SendToProgram(".\n", cps);
3332 IsPromotion(fromX, fromY, toX, toY)
3333 int fromX, fromY, toX, toY;
3335 return gameMode != EditPosition &&
3336 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3337 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3338 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3343 PieceForSquare (x, y)
3347 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3350 return boards[currentMove][y][x];
3354 OKToStartUserMove(x, y)
3357 ChessSquare from_piece;
3360 if (matchMode) return FALSE;
3361 if (gameMode == EditPosition) return TRUE;
3363 if (x >= 0 && y >= 0)
3364 from_piece = boards[currentMove][y][x];
3366 from_piece = EmptySquare;
3368 if (from_piece == EmptySquare) return FALSE;
3370 white_piece = (int)from_piece >= (int)WhitePawn &&
3371 (int)from_piece <= (int)WhiteKing;
3374 case PlayFromGameFile:
3376 case TwoMachinesPlay:
3384 case MachinePlaysWhite:
3385 case IcsPlayingBlack:
3386 if (appData.zippyPlay) return FALSE;
3388 DisplayMoveError(_("You are playing Black"));
3393 case MachinePlaysBlack:
3394 case IcsPlayingWhite:
3395 if (appData.zippyPlay) return FALSE;
3397 DisplayMoveError(_("You are playing White"));
3403 if (!white_piece && WhiteOnMove(currentMove)) {
3404 DisplayMoveError(_("It is White's turn"));
3407 if (white_piece && !WhiteOnMove(currentMove)) {
3408 DisplayMoveError(_("It is Black's turn"));
3411 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3412 /* Editing correspondence game history */
3413 /* Could disallow this or prompt for confirmation */
3416 if (currentMove < forwardMostMove) {
3417 /* Discarding moves */
3418 /* Could prompt for confirmation here,
3419 but I don't think that's such a good idea */
3420 forwardMostMove = currentMove;
3424 case BeginningOfGame:
3425 if (appData.icsActive) return FALSE;
3426 if (!appData.noChessProgram) {
3428 DisplayMoveError(_("You are playing White"));
3435 if (!white_piece && WhiteOnMove(currentMove)) {
3436 DisplayMoveError(_("It is White's turn"));
3439 if (white_piece && !WhiteOnMove(currentMove)) {
3440 DisplayMoveError(_("It is Black's turn"));
3449 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3450 && gameMode != AnalyzeFile && gameMode != Training) {
3451 DisplayMoveError(_("Displayed position is not current"));
3457 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3458 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3459 int lastLoadGameUseList = FALSE;
3460 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3461 ChessMove lastLoadGameStart = (ChessMove) 0;
3465 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3466 int fromX, fromY, toX, toY;
3471 if (fromX < 0 || fromY < 0) return;
3472 if ((fromX == toX) && (fromY == toY)) {
3476 /* Check if the user is playing in turn. This is complicated because we
3477 let the user "pick up" a piece before it is his turn. So the piece he
3478 tried to pick up may have been captured by the time he puts it down!
3479 Therefore we use the color the user is supposed to be playing in this
3480 test, not the color of the piece that is currently on the starting
3481 square---except in EditGame mode, where the user is playing both
3482 sides; fortunately there the capture race can't happen. (It can
3483 now happen in IcsExamining mode, but that's just too bad. The user
3484 will get a somewhat confusing message in that case.)
3488 case PlayFromGameFile:
3490 case TwoMachinesPlay:
3494 /* We switched into a game mode where moves are not accepted,
3495 perhaps while the mouse button was down. */
3498 case MachinePlaysWhite:
3499 /* User is moving for Black */
3500 if (WhiteOnMove(currentMove)) {
3501 DisplayMoveError(_("It is White's turn"));
3506 case MachinePlaysBlack:
3507 /* User is moving for White */
3508 if (!WhiteOnMove(currentMove)) {
3509 DisplayMoveError(_("It is Black's turn"));
3516 case BeginningOfGame:
3519 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3520 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3521 /* User is moving for Black */
3522 if (WhiteOnMove(currentMove)) {
3523 DisplayMoveError(_("It is White's turn"));
3527 /* User is moving for White */
3528 if (!WhiteOnMove(currentMove)) {
3529 DisplayMoveError(_("It is Black's turn"));
3535 case IcsPlayingBlack:
3536 /* User is moving for Black */
3537 if (WhiteOnMove(currentMove)) {
3538 if (!appData.premove) {
3539 DisplayMoveError(_("It is White's turn"));
3540 } else if (toX >= 0 && toY >= 0) {
3543 premoveFromX = fromX;
3544 premoveFromY = fromY;
3545 premovePromoChar = promoChar;
3547 if (appData.debugMode)
3548 fprintf(debugFP, "Got premove: fromX %d,"
3549 "fromY %d, toX %d, toY %d\n",
3550 fromX, fromY, toX, toY);
3556 case IcsPlayingWhite:
3557 /* User is moving for White */
3558 if (!WhiteOnMove(currentMove)) {
3559 if (!appData.premove) {
3560 DisplayMoveError(_("It is Black's turn"));
3561 } else if (toX >= 0 && toY >= 0) {
3564 premoveFromX = fromX;
3565 premoveFromY = fromY;
3566 premovePromoChar = promoChar;
3568 if (appData.debugMode)
3569 fprintf(debugFP, "Got premove: fromX %d,"
3570 "fromY %d, toX %d, toY %d\n",
3571 fromX, fromY, toX, toY);
3581 if (toX == -2 || toY == -2) {
3582 boards[0][fromY][fromX] = EmptySquare;
3583 DrawPosition(FALSE, boards[currentMove]);
3584 } else if (toX >= 0 && toY >= 0) {
3585 boards[0][toY][toX] = boards[0][fromY][fromX];
3586 boards[0][fromY][fromX] = EmptySquare;
3587 DrawPosition(FALSE, boards[currentMove]);
3592 if (toX < 0 || toY < 0) return;
3593 userOfferedDraw = FALSE;
3595 if (appData.testLegality) {
3596 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3597 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3598 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3599 DisplayMoveError(_("Illegal move"));
3603 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3606 if (gameMode == Training) {
3607 /* compare the move played on the board to the next move in the
3608 * game. If they match, display the move and the opponent's response.
3609 * If they don't match, display an error message.
3613 CopyBoard(testBoard, boards[currentMove]);
3614 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3616 if (CompareBoards(testBoard, boards[currentMove+1])) {
3617 ForwardInner(currentMove+1);
3619 /* Autoplay the opponent's response.
3620 * if appData.animate was TRUE when Training mode was entered,
3621 * the response will be animated.
3623 saveAnimate = appData.animate;
3624 appData.animate = animateTraining;
3625 ForwardInner(currentMove+1);
3626 appData.animate = saveAnimate;
3628 /* check for the end of the game */
3629 if (currentMove >= forwardMostMove) {
3630 gameMode = PlayFromGameFile;
3632 SetTrainingModeOff();
3633 DisplayInformation(_("End of game"));
3636 DisplayError(_("Incorrect move"), 0);
3641 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3644 /* Common tail of UserMoveEvent and DropMenuEvent */
3646 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3648 int fromX, fromY, toX, toY;
3649 /*char*/int promoChar;
3651 /* Ok, now we know that the move is good, so we can kill
3652 the previous line in Analysis Mode */
3653 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3654 forwardMostMove = currentMove;
3657 /* If we need the chess program but it's dead, restart it */
3658 ResurrectChessProgram();
3660 /* A user move restarts a paused game*/
3664 thinkOutput[0] = NULLCHAR;
3666 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3668 if (gameMode == BeginningOfGame) {
3669 if (appData.noChessProgram) {
3670 gameMode = EditGame;
3674 gameMode = MachinePlaysBlack;
3676 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3678 if (first.sendName) {
3679 sprintf(buf, "name %s\n", gameInfo.white);
3680 SendToProgram(buf, &first);
3686 /* Relay move to ICS or chess engine */
3687 if (appData.icsActive) {
3688 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3689 gameMode == IcsExamining) {
3690 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3694 if (first.sendTime && (gameMode == BeginningOfGame ||
3695 gameMode == MachinePlaysWhite ||
3696 gameMode == MachinePlaysBlack)) {
3697 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3699 SendMoveToProgram(forwardMostMove-1, &first);
3700 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3701 first.maybeThinking = TRUE;
3703 if (currentMove == cmailOldMove + 1) {
3704 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3708 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3712 switch (MateTest(boards[currentMove], PosFlags(currentMove),
3718 if (WhiteOnMove(currentMove)) {
3719 GameEnds(BlackWins, "Black mates", GE_PLAYER);
3721 GameEnds(WhiteWins, "White mates", GE_PLAYER);
3725 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3730 case MachinePlaysBlack:
3731 case MachinePlaysWhite:
3732 /* disable certain menu options while machine is thinking */
3733 SetMachineThinkingEnables();
3742 HandleMachineMove(message, cps)
3744 ChessProgramState *cps;
3746 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3747 char realname[MSG_SIZ];
3748 int fromX, fromY, toX, toY;
3755 * Kludge to ignore BEL characters
3757 while (*message == '\007') message++;
3760 * Look for book output
3762 if (cps == &first && bookRequested) {
3763 if (message[0] == '\t' || message[0] == ' ') {
3764 /* Part of the book output is here; append it */
3765 strcat(bookOutput, message);
3766 strcat(bookOutput, " \n");
3768 } else if (bookOutput[0] != NULLCHAR) {
3769 /* All of book output has arrived; display it */
3770 char *p = bookOutput;
3771 while (*p != NULLCHAR) {
3772 if (*p == '\t') *p = ' ';
3775 DisplayInformation(bookOutput);
3776 bookRequested = FALSE;
3777 /* Fall through to parse the current output */
3782 * Look for machine move.
3784 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3785 strcmp(buf2, "...") == 0) ||
3786 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3787 strcmp(buf1, "move") == 0)) {
3789 /* This method is only useful on engines that support ping */
3790 if (cps->lastPing != cps->lastPong) {
3791 if (gameMode == BeginningOfGame) {
3792 /* Extra move from before last new; ignore */
3793 if (appData.debugMode) {
3794 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3797 if (appData.debugMode) {
3798 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3799 cps->which, gameMode);
3801 SendToProgram("undo\n", cps);
3807 case BeginningOfGame:
3808 /* Extra move from before last reset; ignore */
3809 if (appData.debugMode) {
3810 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3817 /* Extra move after we tried to stop. The mode test is
3818 not a reliable way of detecting this problem, but it's
3819 the best we can do on engines that don't support ping.
3821 if (appData.debugMode) {
3822 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3823 cps->which, gameMode);
3825 SendToProgram("undo\n", cps);
3828 case MachinePlaysWhite:
3829 case IcsPlayingWhite:
3830 machineWhite = TRUE;
3833 case MachinePlaysBlack:
3834 case IcsPlayingBlack:
3835 machineWhite = FALSE;
3838 case TwoMachinesPlay:
3839 machineWhite = (cps->twoMachinesColor[0] == 'w');
3842 if (WhiteOnMove(forwardMostMove) != machineWhite) {
3843 if (appData.debugMode) {
3845 "Ignoring move out of turn by %s, gameMode %d"
3846 ", forwardMost %d\n",
3847 cps->which, gameMode, forwardMostMove);
3852 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3853 &fromX, &fromY, &toX, &toY, &promoChar)) {
3854 /* Machine move could not be parsed; ignore it. */
3855 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3856 machineMove, cps->which);
3857 DisplayError(buf1, 0);
3858 if (gameMode == TwoMachinesPlay) {
3859 GameEnds(machineWhite ? BlackWins : WhiteWins,
3860 "Forfeit due to illegal move", GE_XBOARD);
3865 hintRequested = FALSE;
3866 lastHint[0] = NULLCHAR;
3867 bookRequested = FALSE;
3868 /* Program may be pondering now */
3869 cps->maybeThinking = TRUE;
3870 if (cps->sendTime == 2) cps->sendTime = 1;
3871 if (cps->offeredDraw) cps->offeredDraw--;
3874 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3876 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3880 /* currentMoveString is set as a side-effect of ParseOneMove */
3881 strcpy(machineMove, currentMoveString);
3882 strcat(machineMove, "\n");
3883 strcpy(moveList[forwardMostMove], machineMove);
3885 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3887 if (gameMode == TwoMachinesPlay) {
3888 if (cps->other->sendTime) {
3889 SendTimeRemaining(cps->other,
3890 cps->other->twoMachinesColor[0] == 'w');
3892 SendMoveToProgram(forwardMostMove-1, cps->other);
3895 if (cps->other->useColors) {
3896 SendToProgram(cps->other->twoMachinesColor, cps->other);
3898 SendToProgram("go\n", cps->other);
3900 cps->other->maybeThinking = TRUE;