home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC-Online 1996 May
/
PCOnline_05_1996.bin
/
linux
/
source
/
xap
/
gchess
/
xboard-3.0
/
xboard-3
/
xboard-3.0.pl9
/
backend.c
next >
Wrap
C/C++ Source or Header
|
1993-09-09
|
111KB
|
4,254 lines
/*
* backend.c -- Common back end for X and Windows NT versions of XBoard
* $Id: backend.c,v 1.22 1993/09/10 01:24:43 mann Exp $
*
* Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
* Enhancements Copyright 1992-93 Free Software Foundation, Inc.
*
* XBoard borrows its colors, icon and piece bitmaps from XChess
* which was written and is copyrighted by Wayne Christopher.
*
* The following terms apply to Digital Equipment Corporation's copyright
* interest in XBoard:
* ------------------------------------------------------------------------
* All Rights Reserved
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appear in all copies and that
* both that copyright notice and this permission notice appear in
* supporting documentation, and that the name of Digital not be
* used in advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
* DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
* ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
* WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
* SOFTWARE.
* ------------------------------------------------------------------------
*
* The following terms apply to the enhanced version of XBoard distributed
* by the Free Software Foundation:
* ------------------------------------------------------------------------
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* ------------------------------------------------------------------------
*
* See the file ChangeLog for a revision history.
*/
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#if (defined(__STDC__) || defined(WIN32)) && !defined(ESIX)
#include <stdlib.h>
#endif
#if defined(SYSTEM_FIVE) || defined(SYSV)
#include <sys/types.h>
#include <sys/stat.h>
#ifdef AIXV3
#include <fcntl.h>
#else
#include <sys/fcntl.h>
#endif /*AIXV3*/
#ifdef SVR4
#include <stropts.h>
#ifdef sun
#include <sys/systeminfo.h>
#endif
#endif
#endif
#if defined(__STDC__) || defined(SYSTEM_FIVE) || defined(SYSV) || defined(sun) || defined(WIN32)
#include <string.h>
#else
#include <strings.h>
#endif
#include <math.h>
#include "common.h"
#include "frontend.h"
#include "backend.h"
#include "parser.h"
int establish P((void));
void read_from_player P((InputSourceRef isr, char *buf, int count, int error));
void read_from_ics P((InputSourceRef isr, char *buf, int count, int error));
void SendToICS P((char *s));
ChessSquare CharToPiece P((int c));
char PieceToChar P((ChessSquare p));
ChessSquare PromoPiece P((ChessMove moveType));
ChessMove CoordsToAlgebraic P((int fromX, int fromY, int toX, int toY,
int promoChar, int currentBoardIndex,
char out[MOVE_LEN]));
void InitPosition P((int redraw));
void CopyBoard P((Board to, Board from));
Boolean CompareBoards P((Board board1, Board board2));
void SendCurrentBoard P((ProcRef pr));
void SendBoard P((ProcRef pr, Board board));
void FinishUserMove P((ChessMove moveType, int toX, int toY));
void HandleMachineMove P((char *message, InputSourceRef isr));
void LoadGameLoop P((void));
int LoadGameOneMove P((void));
int LoadGameFromFile P((char *filename, int n));
int LoadPositionFromFile P((char *filename, int n));
int SaveGameToFile P((char *filename));
int SavePositionToFile P((char *filename));
void ApplyMove P((ChessMove *moveType, int fromX, int fromY,
int toX, int toY, Board board));
void MakeMove P((ChessMove *moveType, int fromX, int fromY,
int toX, int toY));
void GameEnds P((char *message));
void ShutdownChessPrograms P((char *message));
void Reset P((int redraw));
void EditPositionDone P((void));
void PrintOpponents P((FILE *fp));
void PrintPosition P((FILE *fp, int move));
void InitChessProgram P((char *hostName, char *programName,
ProcRef *pr, InputSourceRef *isr, int *sendTime));
void SendToProgram P((char *message, ProcRef pr));
void ReceiveFromProgram P((InputSourceRef isr, char *buf, int count, int error));
void SendSearchDepth P((ProcRef pr));
void SendTimeRemaining P((ProcRef pr));
void Attention P((ProcRef pr));
char *StrStr P((char *string, char *match));
int ToLower P((int c));
int ToUpper P((int c));
Boolean ParseMachineMove P((char *machineMove, int moveNum,
ChessMove *moveType, int *fromX, int *fromY,
int *toX, int *toY, char *promoChar));
void ParseGameHistory P((char *game));
void ParseBoard8 P((char *string));
void StartClocks P((void));
void DisplayBothClocks P((void));
void SwitchClocks P((void));
void StopClocks P((void));
void ResetClocks P((void));
/* States for ics_getting_history */
#define H_FALSE 0
#define H_REQUESTED 1
#define H_GOT_REQ_HEADER 2
#define H_GOT_UNREQ_HEADER 3
#define H_GETTING_MOVES 4
FILE *gameFileFP,
*fromUserFP = stdin, *toUserFP = stdout, *debugFP = stderr;
int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0,
firstMove = TRUE, flipView = FALSE,
blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE,
searchTime = 0, pausing = FALSE,
whiteFlag = FALSE, blackFlag = FALSE, maybeThinking = FALSE,
ics_user_moved = 0, ics_gamenum = -1,
ics_getting_history = H_FALSE, matchMode = FALSE;
ProcRef firstProgramPR = NoProc, secondProgramPR = NoProc, icsPR = NoProc,
lastMsgPR = NoProc;
InputSourceRef firstProgramISR = NULL, secondProgramISR = NULL,
telnetISR = NULL, fromUserISR = NULL;
int firstSendTime = 2, secondSendTime = 2; /* 0=don't, 1=do, 2=test first*/
GameMode gameMode = BeginningOfGame, lastGameMode = BeginningOfGame;
char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2],
ptyname[24], *chessDir, *programName, ics_black[32], ics_white[32],
endMessage[MOVE_LEN * 4];
char *commentList[MAX_MOVES];
long whiteTimeRemaining, blackTimeRemaining, timeControl;
long timeRemaining[2][MAX_MOVES];
extern char currentMoveString[];
#ifdef FLEX
extern char *yytext;
#else
extern char yytext[];
#endif
extern int yyboardindex;
Board boards[MAX_MOVES], initialPosition = {
{ WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
{ WhitePawn, WhitePawn, WhitePawn, WhitePawn,
WhitePawn, WhitePawn, WhitePawn, WhitePawn },
{ EmptySquare, EmptySquare, EmptySquare, EmptySquare,
EmptySquare, EmptySquare, EmptySquare, EmptySquare },
{ EmptySquare, EmptySquare, EmptySquare, EmptySquare,
EmptySquare, EmptySquare, EmptySquare, EmptySquare },
{ EmptySquare, EmptySquare, EmptySquare, EmptySquare,
EmptySquare, EmptySquare, EmptySquare, EmptySquare },
{ EmptySquare, EmptySquare, EmptySquare, EmptySquare,
EmptySquare, EmptySquare, EmptySquare, EmptySquare },
{ BlackPawn, BlackPawn, BlackPawn, BlackPawn,
BlackPawn, BlackPawn, BlackPawn, BlackPawn },
{ BlackRook, BlackKnight, BlackBishop, BlackQueen,
BlackKing, BlackBishop, BlackKnight, BlackRook }
};
AppData appData;
char pieceToChar[] = {
'P', 'R', 'N', 'B', 'Q', 'K',
'p', 'r', 'n', 'b', 'q', 'k', '.'
};
void InitBackEnd1()
{
char buf[MSG_SIZ];
int matched, min, sec;
/*
* Internet chess server status
*/
if (appData.icsActive) {
appData.matchMode = FALSE;
appData.noChessProgram = TRUE;
}
/*
* Parse timeControl resource
*/
if (!ParseTimeControl(appData.timeControl)) {
sprintf(buf, "Bad timeControl option %s",
appData.timeControl);
DisplayFatalError(buf, 0);
ExitEvent(2);
}
if (appData.icsActive) timeControl = 0;
/*
* Parse searchTime resource
*/
if (*appData.searchTime != NULLCHAR) {
matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
if (matched == 1) {
searchTime = min * 60;
} else if (matched == 2) {
searchTime = min * 60 + sec;
} else {
sprintf(buf, "Bad searchTime option %s",
appData.searchTime);
DisplayFatalError(buf, 0);
ExitEvent(2);
}
}
if ((*appData.searchTime != NULLCHAR) || (appData.searchDepth > 0)
|| appData.noChessProgram)
appData.clockMode = FALSE;
if (appData.icsActive) appData.clockMode = TRUE;
}
int ParseTimeControl(tc)
char *tc;
{
int matched, min, sec;
matched = sscanf(tc, "%d:%d", &min, &sec);
if (matched == 1) {
timeControl = min * 60 * 1000;
} else if (matched == 2) {
timeControl = (min * 60 + sec) * 1000;
} else {
return FALSE;
}
return TRUE;
}
void InitBackEnd2()
{
char buf[MSG_SIZ];
int err;
if (appData.icsActive) {
err = establish();
if (err != 0) {
sprintf(buf,
"Could not connect to host %s, port %d",
appData.icsHost, appData.icsPort);
DisplayFatalError(buf, err);
ExitEvent(1);
}
SetICSMode();
telnetISR = AddInputSource(icsPR, FALSE, read_from_ics);
fromUserISR = AddInputSource(NoProc, FALSE, read_from_player);
} else {
if (appData.noChessProgram)
SetNCPMode();
else
SetGNUMode();
}
/*
* If there is to be a machine match, set it up.
*/
if (appData.matchMode) {
if (appData.noChessProgram) {
DisplayFatalError("Can't have a match with no chess programs", 0);
ExitEvent(2);
}
Reset(TRUE);
matchMode = TRUE;
if (*appData.loadGameFile != NULLCHAR) {
if (!LoadGameFromFile(appData.loadGameFile,
appData.loadGameIndex)) {
DisplayFatalError("Bad game file", 0);
ExitEvent(1);
}
} else if (*appData.loadPositionFile != NULLCHAR) {
if (!LoadPositionFromFile(appData.loadPositionFile,
appData.loadPositionIndex)) {
DisplayFatalError("Bad position file", 0);
ExitEvent(1);
}
}
TwoMachinesEvent();
} else {
Reset(TRUE);
if (*appData.loadGameFile != NULLCHAR) {
(void) LoadGameFromFile(appData.loadGameFile,
appData.loadGameIndex);
} else if (*appData.loadPositionFile != NULLCHAR) {
(void) LoadPositionFromFile(appData.loadPositionFile,
appData.loadPositionIndex);
}
}
}
/*
* Establish will establish a contact to a remote host.port.
* Sets icsPR to a ProcRef for a process (or pseudo-process)
* used to talk to the host.
* Returns 0 if okay, error code if not.
*/
int establish()
{
char buf[MSG_SIZ];
if (*appData.icsCommPort != NULLCHAR) {
/* Talk to the host through a serial comm port */
Raw();
return OpenCommPort(appData.icsCommPort, &icsPR);
} else if (*appData.gateway != NULLCHAR) {
/* Use rsh to run telnet program on a gateway host */
sprintf(buf, "%s %s %s %s %d", appData.remoteShell,
appData.gateway, appData.telnetProgram,
appData.icsHost, appData.icsPort);
return StartChildProcess(buf, &icsPR);
} else if (appData.useTelnet) {
Raw();
return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
} else {
/* TCP socket interface differs somewhat between
Unix and NT; handle details in the front end.
*/
return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
}
}
void read_from_player(isr, message, count, error)
InputSourceRef isr;
char *message;
int count;
int error;
{
int outError, outCount;
if (count > 0) {
outCount = OutputToProcess(icsPR, message, count, &outError);
if (outCount < count) {
DisplayFatalError("Error writing to ICS", outError);
ExitEvent(1);
}
} else if (count < 0) {
DisplayFatalError("Error reading from keyboard", error);
ExitEvent(1);
} else {
DisplayFatalError("Got end of file from keyboard", 0);
ExitEvent(0);
}
}
void SendToICS(s)
char *s;
{
int count, outCount, outError;
if (appData.debugMode)
fprintf(debugFP, "Sending to ICS: %s", s);
count = strlen(s);
outCount = OutputToProcess(icsPR, s, count, &outError);
if (outCount < count) {
DisplayFatalError("Error writing to ICS", outError);
ExitEvent(1);
}
}
static int leftover_start = 0, leftover_len = 0;
char star_match[STAR_MATCH_N][MSG_SIZ];
/* Test whether pattern is present at &buf[*index]; if so, return TRUE,
advance *index beyond it, and set leftover_start to the new value of
*index; else return FALSE. If pattern contains the character '*', it
matches any sequence of characters not containing '\r', '\n', or the
character following the '*' (if any), and the matched sequence(s) are
copied into star_match.
*/
int looking_at(buf, index, pattern)
char *buf;
int *index;
char *pattern;
{
char *bufp = &buf[*index], *patternp = pattern;
int star_count = 0;
char *matchp = star_match[0];
for (;;) {
if (*patternp == NULLCHAR) {
*index = leftover_start = bufp - buf;
*matchp = NULLCHAR;
return TRUE;
}
if (*bufp == NULLCHAR) return FALSE;
if (*patternp == '*') {
if (*bufp == *(patternp + 1)) {
*matchp = NULLCHAR;
matchp = star_match[++star_count];
patternp += 2;
bufp++;
continue;
} else if (*bufp == '\n' || *bufp == '\r') {
patternp++;
if (*patternp == NULLCHAR)
continue;
else
return FALSE;
} else {
*matchp++ = *bufp++;
continue;
}
}
if (*patternp != *bufp) return FALSE;
patternp++;
bufp++;
}
}
void read_from_ics(isr, data, count, error)
InputSourceRef isr;
char *data;
int count;
int error;
{
#define BUF_SIZE 8192
#define STARTED_NONE 0
#define STARTED_BOARD 1
#define STARTED_MOVES 2
static int started = STARTED_NONE;
static char parse[20000];
static int parse_pos;
static char buf[BUF_SIZE + 1];
char str[500];
int i, oldi;
int buf_len;
int next_out;
if (count > 0) {
/* If last read ended with a partial line that we couldn't parse,
prepend it to the new read and try again. */
if (leftover_len > 0) {
for (i=0; i<leftover_len; i++)
buf[i] = buf[leftover_start + i];
}
memcpy(&buf[leftover_len], data, count);
buf_len = count + leftover_len;
buf[buf_len] = NULLCHAR;
next_out = leftover_len;
leftover_start = 0;
i = 0;
while (i < buf_len) {
#ifdef ZIPPY
if (ZippyConverse(buf, &i)) continue;
#else
/* Skip over what people say */
if (looking_at(buf, &i, "shouts: *") ||
looking_at(buf, &i, "tells you: *") ||
looking_at(buf, &i, "says: *") ||
looking_at(buf, &i, "whispers: *") ||
looking_at(buf, &i, "kibitzes: *")) {
continue;
}
#endif /*ZIPPY*/
#define WILL_ECHO "\377\373\1"
#define WONT_ECHO "\377\374\1"
#define DO_ECHO "\377\375\1"
#define DONT_ECHO "\377\376\1"
/* Kludge to deal with part of TELNET protocol */
if (looking_at(buf, &i, WILL_ECHO)) {
SendToICS(DO_ECHO);
EchoOff();
}
if (looking_at(buf, &i, WONT_ECHO)) {
SendToICS(DONT_ECHO);
EchoOn();
}
if (looking_at(buf, &i,
"a b c d e f g h") ||
looking_at(buf, &i,
"h g f e d c b a")) {
/* End of board style 1 */
SendToICS("set style 8\n");
SendToICS("refresh\n");
continue;
}
oldi = i;
if (looking_at(buf, &i, "#@#")) {
started = STARTED_BOARD;
parse_pos = 0;
if (oldi > next_out)
fwrite(&buf[next_out], oldi - next_out, 1, toUserFP);
continue;
}
if (started == STARTED_BOARD && looking_at(buf, &i, "@#@")) {
/* Board read is done */
started = STARTED_NONE;
next_out = i;
parse[parse_pos] = NULLCHAR;
/* Parse and display the board */
ParseBoard8(parse);
ics_user_moved = 0;
continue;
}
if (looking_at(buf, &i, "* *vs. * *--- *")) {
/* Header for a move list -- first line */
/* We might want to save some of these fields but
for now we don't */
switch (ics_getting_history) {
case H_FALSE:
switch (gameMode) {
case IcsIdle:
case BeginningOfGame:
/* User typed "moves" or "oldmoves" while we
were idle. Pretend we asked for these
moves and soak them up so user can step
through them and/or save them.
*/
Reset(FALSE);
gameMode = IcsObserving;
ModeHighlight();
ics_gamenum = -1;
ics_getting_history = H_GOT_REQ_HEADER;
break;
case ForceMoves: /*?*/
case EditPosition: /*?*/
/* Should above feature work in these modes too? */
/* For now it doesn't */
ics_getting_history = H_GOT_UNREQ_HEADER;
break;
default:
ics_getting_history = H_GOT_UNREQ_HEADER;
break;
}
break;
case H_REQUESTED:
/* All is well */
ics_getting_history = H_GOT_REQ_HEADER;
break;
case H_GOT_REQ_HEADER:
case H_GOT_UNREQ_HEADER:
case H_GETTING_MOVES:
/* Should not happen */
DisplayError("Error gathering move list", 0);
ics_getting_history = H_GOT_UNREQ_HEADER;
break;
}
continue;
}
if (looking_at(buf, &i,
"* * match, initial time: * minutes, increment: * seconds.")) {
/* Header for a move list -- second line */
/* Initial board will follow if this is a wild game */
/* Again, we might want to save some of these fields later */
/* For now we do nothing with them. */
continue;
}
oldi = i;
if (looking_at(buf, &i, "Move ")) {
/* Beginning of a move list */
switch (ics_getting_history) {
case H_FALSE:
/* Normally should not happen */
/* Maybe user hit reset while we were parsing */
break;
case H_REQUESTED:
case H_GETTING_MOVES:
/* Should not happen */
DisplayError("Error gathering move list", 0);
break;
case H_GOT_REQ_HEADER:
ics_getting_history = H_GETTING_MOVES;
started = STARTED_MOVES;
parse_pos = 0;
if (oldi > next_out)
fwrite(&buf[next_out], oldi - next_out, 1, toUserFP);
break;
case H_GOT_UNREQ_HEADER:
ics_getting_history = H_FALSE;
break;
}
continue;
}
if(looking_at(buf, &i, "% ")) {
switch (started) {
case STARTED_NONE:
#ifdef ZIPPY
ZippyPeriodic();
#endif /*ZIPPY*/
continue;
case STARTED_BOARD:
/* Something went wrong; found a prompt while
accumulating a board */
started = STARTED_NONE;
DisplayError("Error gathering board", 0);
continue;
case STARTED_MOVES:
started = STARTED_NONE;
parse[parse_pos] = NULLCHAR;
ParseGameHistory(parse);
if (gameMode == IcsObserving && ics_gamenum == -1) {
/* Moves came from oldmoves or moves command
while we weren't doing anything else.
*/
currentMove = forwardMostMove;
flipView = appData.flipView;
DrawPosition(FALSE, boards[currentMove]);
DisplayBothClocks();
sprintf(str, "%s vs. %s", ics_white, ics_black);
DisplayTitle(str);
gameMode = IcsIdle;
}
DisplayMove(currentMove - 1);
SendToICS("\n"); /*kludge: force a prompt*/
next_out = i;
ics_getting_history = H_FALSE;
continue;
}
}
if (started != STARTED_NONE && i >= leftover_len) {
/* Accumulate characters in board
or move list*/
if (buf[i] != '\r' && buf[i] != NULLCHAR)
parse[parse_pos++] = buf[i];
}
/* Start of game messages. Mostly we detect start of game
when the first board image arrives, but we need to prime
the pump for games we're just observing. */
if (looking_at(buf, &i, "Adding game * to observation list")) {
sprintf(str, "refresh %d\n", atoi(star_match[0]));
SendToICS(str);
continue;
}
/* Error messages */
if (ics_user_moved) {
if (looking_at(buf, &i, "No such command") ||
looking_at(buf, &i, "Illegal move") ||
looking_at(buf, &i, "Not a legal move") ||
looking_at(buf, &i, "Your king is in check") ||
looking_at(buf, &i, "It isn't your turn")) {
/**** Illegal move ****/
ics_user_moved = 0;
if (forwardMostMove > backwardMostMove) {
currentMove = --forwardMostMove;
DisplayError("Illegal move", 0);
DrawPosition(FALSE, boards[currentMove]);
SwitchClocks();
}
continue;
}
}
if (looking_at(buf, &i, "You and your opponent still have time")) {
/* We must have called his flag a little too soon */
whiteFlag = blackFlag = FALSE;
continue;
}
/* Start/end-of-game messages */
if (looking_at(buf, &i, "{Game * (* vs. *)* * *}")) {
/* New style generic game start/end messages */
/* star_match[0] is the game number */
/* [1] is the white player's name */
/* [2] is the black player's name */
/* [3] is either ":" or empty (don't care) */
/* [4] is usually the loser's name or a noise word */
/* [5] contains the reason for the game end */
int gamenum = atoi(star_match[0]);
char *white = star_match[1];
char *loser = star_match[4];
char *why = star_match[5];
#ifdef ZIPPY
/* Game start messages */
if (strcmp(loser, "Creating") == 0 ||
strcmp(loser, "Continuing") == 0) {
ZippyGameStart(white, star_match[2]);
continue;
}
#endif /*ZIPPY*/
/* Game end messages */
if (ics_gamenum != gamenum) continue;
if (StrStr(why, "checkmate")) {
if (strcmp(loser, white) == 0)
GameEnds("Black mates");
else
GameEnds("White mates");
} else if (StrStr(why, "resign")) {
if (strcmp(loser, white) == 0)
GameEnds("White resigns");
else
GameEnds("Black resigns");
} else if (StrStr(why, "forfeits on time")) {
if (strcmp(loser, white) == 0)
GameEnds("Black wins on time");
else
GameEnds("White wins on time");
} else if (StrStr(why, "stalemate")) {
GameEnds("Stalemate");
} else if (StrStr(why, "drawn by mutual agreement")) {
GameEnds("Draw agreed");
} else if (StrStr(why, "repetition")) {
GameEnds("Draw by repetition");
} else if (StrStr(why, "50")) {
GameEnds("Draw (50 move rule)");
} else if (StrStr(why, "neither player has mating")) {
GameEnds("Draw (insufficient material)");
} else if (StrStr(why, "no material")) {
GameEnds("Draw (insufficient material to win on time)");
} else if (StrStr(why, "time")) {
GameEnds("Draw (both players ran out of time)");
} else if (StrStr(why, "disconnected and forfeits")) {
/* in this case the word "abuser" preceded the loser */
loser = why;
why = strchr(loser, ' ');
*why++ = NULLCHAR;
if (strcmp(loser, white) == 0)
GameEnds("Black wins (forfeit)");
else
GameEnds("White wins (forfeit)");
} else if (StrStr(why, "assert")) {
/* "loser" is actually the winner in this case */
if (strcmp(loser, white) == 0)
GameEnds("White asserts a win");
else
GameEnds("Black asserts a win");
} else if (StrStr(why, "aborted")) {
GameEnds("Game aborted");
} else if (StrStr(why, "removed")) {
GameEnds("Game aborted");
} else if (StrStr(why, "adjourn")) {
GameEnds("Game adjourned");
}
continue;
}
if (looking_at(buf, &i, "Removing game * from observation list")) {
if (gameMode == IcsObserving &&
atoi(star_match[0]) == ics_gamenum)
{
StopClocks();
gameMode = IcsIdle;
ics_gamenum = -1;
ics_user_moved = FALSE;
}
continue;
}
/* Advance leftover_start past any newlines we find,
so only partial lines can get reparsed */
if (looking_at(buf, &i, "\n")) continue;
if (looking_at(buf, &i, "\r")) {
#ifdef WIN32
/* Kludge; change \r\000 to \r\r */
if (i < buf_len && buf[i] == NULLCHAR) buf[i++] = '\r';
#endif
continue;
}
i++; /* skip unparsed character and loop back */
}
if (started == STARTED_NONE && i > next_out)
fwrite(&buf[next_out], i - next_out, 1, toUserFP);
leftover_len = buf_len - leftover_start;
/* if buffer ends with something we couldn't parse,
reparse it after appending the next read */
} else if (count == 0) {
DisplayFatalError("Connection closed by ICS", 0);
ExitEvent(0);
} else {
DisplayFatalError("Error reading from ICS", error);
ExitEvent(1);
}
}
/*
ICS board style 8 looks like this:
#@#000observer :aaa :RNBQKBNRPPPPPPPP pppppppprnbqkbnr001W39390360003600@#@
Information offsets, descriptions and lengths:
+3 Game # (3)
+6 White's name (16 + ':' = 17)
+23 Black's name (16 + ':' = 17)
+40 Board (64)
+104 Move # (3)
+107 Whose move (1)
+108 White Strength (2)
+110 Black Strength (2)
+112 White Time (5)
+117 Black Time (5)
+122 Move string (variable
A "*" instead of a ":" after the name implies that the person using xboard
is playing the game.
The move string is either empty or consists of a move followed by
elapsed time in parentheses.
The pattern defined below doesn't include the #@# and @#@ brackets,
and it assumes the board string is null-terminated. ParseBoard8's
caller takes care of this.
*/
#define PATTERN "%3d%16s %1c%16s %1c%64c%3d%1c%2d%2d%5d%5d%s %s"
void ParseBoard8(string)
char *string;
{
GameMode newGameMode;
int gamenum;
int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
char playing_white, playing_black, to_play, board_chars[64];
char move_str[500], str[500], elapsed_time[500];
char black[32], white[32];
Board board;
if (appData.debugMode)
fprintf(debugFP, "Parsing board: %s\n", string);
move_str[0] = NULLCHAR;
elapsed_time[0] = NULLCHAR;
n = sscanf(string, PATTERN, &gamenum, white, &playing_white,
black, &playing_black, board_chars, &moveNum, &to_play,
&white_stren, &black_stren, &white_time, &black_time,
move_str, elapsed_time);
if (n < 12) {
sprintf(str, "Failed to parse board string: \"%s\"", string);
DisplayError(str, 0);
return;
}
if (playing_white == '*')
newGameMode = IcsPlayingWhite;
else if (playing_black == '*')
newGameMode = IcsPlayingBlack;
else
newGameMode = IcsObserving;
/* Convert the move number to internal form */
moveNum = (moveNum - 1) * 2;
if (to_play == 'B') moveNum++;
/* Deal with initial board display on move listing
of wild games.
*/
switch (ics_getting_history) {
case H_FALSE:
case H_REQUESTED:
break;
case H_GOT_REQ_HEADER:
/* This is an initial board that we want */
gamenum = ics_gamenum;
moveNum = 0; /* !! ICS bug workaround */
break;
case H_GOT_UNREQ_HEADER:
/* This is an initial board that we don't want */
return;
case H_GETTING_MOVES:
/* Should not happen */
DisplayError("Error gathering move list", 0);
return;
}
/* Take action if this is the first board of a new game */
if (gamenum != ics_gamenum) {
/* Check if trying to do two things at once */
if (gameMode == IcsObserving) {
/* Error: xboard can't handle two games at once */
/* Stop observing the old game */
sprintf(str, "observe %d\n", ics_gamenum);
SendToICS(str);
sprintf(str, "Aren't you observing game %d? Attempting to stop observing it.",
ics_gamenum);
DisplayError(str, 0);
/* continue as in normal case */
} else if (gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
/* Error: xboard can't handle two games at once */
if (newGameMode == IcsObserving) {
/* Stop observing the new game */
SendToICS("observe\n");
sprintf(str, "Aren't you playing a game? Attempting to stop observing game %d.",
gamenum);
DisplayError(str, 0);
/* ignore this board */
return;
} else /* newGameMode == IcsPlaying(White|Black) */ {
/* Playing two games??? ICS supposedly can't do this. */
sprintf(str, "BUG: playing two games at once (%d and %d)",
ics_gamenum, gamenum);
DisplayError(str, 0);
/* continue as in normal case, hoping old game is gone */
}
}
/* Normal case, or error recovered */
Reset(FALSE);
if (moveNum > 0) {
/* Need to get game history */
ics_getting_history = H_REQUESTED;
sprintf(str, "moves %d\n", gamenum);
SendToICS(str);
}
}
/* Initially flip the board to have black on the bottom iff playing
black, but let the user change it with the Flip View button. */
if (gameMode == IcsIdle || gameMode == BeginningOfGame)
flipView = (newGameMode == IcsPlayingBlack);
/* Update known move number limits */
if (gameMode == IcsIdle || gameMode == BeginningOfGame) {
forwardMostMove = backwardMostMove = currentMove = moveNum;
} else if (moveNum > forwardMostMove) {
forwardMostMove = moveNum;
if (!pausing)
currentMove = moveNum;
}
/* Done with values from previous mode; copy in new ones */
gameMode = newGameMode;
ModeHighlight();
ics_gamenum = gamenum;
strcpy(ics_white, white);
strcpy(ics_black, black);
/* Parse the board */
for (k = 0; k < 8; k++)
for (j = 0; j < 8; j++)
board[k][j] = CharToPiece(board_chars[k*8 + j]);
CopyBoard(boards[moveNum], board);
if (moveNum == 0) {
startedFromSetupPosition =
!CompareBoards(board, initialPosition);
}
/* Put the move on the move list, first converting
to canonical algebraic form. */
if (moveNum > 0) {
ChessMove moveType;
int fromX, fromY, toX, toY;
char promoChar;
if (moveNum - 1 < backwardMostMove) {
/* We don't know what the board looked like before
this move. Punt. */
strcpy(parseList[moveNum - 1], move_str);
} else {
if (ParseMachineMove(move_str, moveNum - 1, &moveType,
&fromX, &fromY, &toX, &toY, &promoChar)) {
/* Work around ICS bug: pawn promotion is not indicated,
even if underpromoted. Unfortunately there is no
workaround for the same bug when it bites us in
ParseGameHistory().
*/
if (move_str[0] == 'P' && (toY == 0 || toY == 7))
promoChar = ToLower(PieceToChar(board[toY][toX]));
(void) CoordsToAlgebraic(fromX, fromY, toX, toY, promoChar,
moveNum - 1, parseList[moveNum - 1]);
strcat(parseList[moveNum - 1], " ");
strcat(parseList[moveNum - 1], elapsed_time);
} else {
/* Move from ICS was illegal!? Punt. */
strcpy(parseList[moveNum - 1], move_str);
}
}
}
if (ics_getting_history == H_GOT_REQ_HEADER) {
/* This was an initial position from a move list, not
the current position, so don't display it */
return;
}
/* Update and display the clocks */
timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
StartClocks();
/* Display opponents and material strengths */
sprintf(str, "%s (%d) vs. %s (%d)",
ics_white, white_stren, ics_black, black_stren);
DisplayTitle(str);
/* Display the board */
if (!pausing) {
CommentPopDown();
DrawPosition(FALSE, boards[currentMove]);
DisplayMove(moveNum - 1);
if (appData.ringBellAfterMoves && !ics_user_moved)
RingBell();
}
}
void ProcessICSInitScript(f)
FILE *f;
{
char buf[MSG_SIZ];
while (fgets(buf, MSG_SIZ, f)) {
SendToICS(buf);
}
fclose(f);
}
char PieceToChar(p)
ChessSquare p;
{
return pieceToChar[(int) p];
}
ChessSquare CharToPiece(c)
int c;
{
switch (c) {
default:
case '.': return EmptySquare;
case 'P': return WhitePawn;
case 'R': return WhiteRook;
case 'N': return WhiteKnight;
case 'B': return WhiteBishop;
case 'Q': return WhiteQueen;
case 'K': return WhiteKing;
case 'p': return BlackPawn;
case 'r': return BlackRook;
case 'n': return BlackKnight;
case 'b': return BlackBishop;
case 'q': return BlackQueen;
case 'k': return BlackKing;
}
}
ChessSquare PromoPiece(moveType)
ChessMove moveType;
{
switch (moveType) {
default:
return EmptySquare;
case WhitePromotionQueen:
return WhiteQueen;
case BlackPromotionQueen:
return BlackQueen;
case WhitePromotionRook:
return WhiteRook;
case BlackPromotionRook:
return BlackRook;
case WhitePromotionBishop:
return WhiteBishop;
case BlackPromotionBishop:
return BlackBishop;
case WhitePromotionKnight:
return WhiteKnight;
case BlackPromotionKnight:
return BlackKnight;
}
}
/* Convert coordinates to normal algebraic notation.
promoChar must be NULLCHAR or '.' if not a promotion.
*/
ChessMove CoordsToAlgebraic(fromX, fromY, toX, toY, promoChar,
currentBoardIndex, out)
int fromX, fromY, toX, toY;
int promoChar;
int currentBoardIndex;
char out[MOVE_LEN];
{
ChessSquare piece;
ChessMove ret;
char *outp = out;
int i;
if (promoChar == '.') promoChar = NULLCHAR;
piece = boards[currentBoardIndex][fromY][fromX];
switch (piece) {
case WhitePawn:
case BlackPawn:
/* Pawn move */
*outp++ = fromX + 'a';
if (fromX == toX) {
/* Non-capture; use style "e5" or "e8Q" */
*outp++ = toY + '1';
*outp++ = ToUpper(promoChar);
*outp = NULLCHAR;
} else {
/* Capture; use style "exd5" or "exd8Q" */
*outp++ = 'x';
*outp++ = toX + 'a';
*outp++ = toY + '1';
*outp++ = ToUpper(promoChar);
*outp = NULLCHAR;
}
/* Test if okay by parsing; this notation should parse
unambiguously if the original move was legal. More
code would be needed if we wanted the style "ed" for
captures, since that can be ambiguous.
*/
ret = yylexstr(currentBoardIndex, out);
break;
case WhiteKing:
case BlackKing:
/* Test for castling or ICS wild castling */
/* Use style "0-0" (zero-zero) */
if (fromY == toY &&
fromY == ((piece == WhiteKing) ? 0 : 7) &&
((fromX == 4 && (toX == 2 || toX == 6)) ||
(fromX == 3 && (toX == 1 || toX == 5)))) {
switch (toX) {
case 1:
case 6:
strcpy(out, "0-0");
break;
case 2:
case 5:
strcpy(out, "0-0-0");
break;
}
ret = yylexstr(currentBoardIndex, out);
break;
}
/* else fall through */
default:
/* Piece move */
/* First try style "Nf3" or "Nxf7" */
*outp++ = ToUpper(PieceToChar(piece));
/* Capture? */
if(boards[currentBoardIndex][toY][toX] != EmptySquare)
*outp++ = 'x';
*outp++ = toX + 'a';
*outp++ = toY + '1';
*outp = NULLCHAR;
/* Test if ambiguous */
ret = yylexstr(currentBoardIndex, out);
if (ret != AmbiguousMove) break;
/* Try style "Ngf3" or "Nexf7" */
for (i=4; i>=1; i--) out[i+1] = out[i];
out[1] = fromX + 'a';
/* Test if ambiguous */
ret = yylexstr(currentBoardIndex, out);
if (ret != AmbiguousMove) break;
/* Try style "N1f3" */
out[1] = fromY + '1';
/* Test if ambiguous */
ret = yylexstr(currentBoardIndex, out);
if (ret != AmbiguousMove) break;
/* Try style "Ng1f3" or "Ne5xf7" */
/* Can be needed iff there are 3 or more pieces of the
type being moved on the board, due to promotion */
for (i=5; i>=2; i--) out[i+1] = out[i];
out[1] = fromX + 'a';
out[2] = fromY + '1';
/* Test if okay */
ret = yylexstr(currentBoardIndex, out);
break;
case EmptySquare:
/* Illegal move; use coordinate notation */
*outp++ = fromX + 'a';
*outp++ = fromY + '1';
*outp++ = toX + 'a';
*outp++ = toY + '1';
*outp++ = ToUpper(promoChar);
*outp = NULLCHAR;
return BadMove;
}
switch (ret) {
case NormalMove:
case WhitePromotionKnight:
case WhitePromotionBishop:
case WhitePromotionRook:
case WhitePromotionQueen:
case BlackPromotionKnight:
case BlackPromotionBishop:
case BlackPromotionRook:
case BlackPromotionQueen:
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
case WhiteKingSideCastle:
case WhiteQueenSideCastle:
case BlackKingSideCastle:
case BlackQueenSideCastle:
case WhiteKingSideCastleWild:
case WhiteQueenSideCastleWild:
case BlackKingSideCastleWild:
case BlackQueenSideCastleWild:
if (currentMoveString[0] != fromX + 'a' ||
currentMoveString[1] != fromY + '1' ||
currentMoveString[2] != toX + 'a' ||
currentMoveString[3] != toY + '1' ||
(promoChar != NULLCHAR &&
currentMoveString[4] != ToLower(promoChar))) {
/* Illegal move; use coordinate notation */
outp = out;
*outp++ = fromX + 'a';
*outp++ = fromY + '1';
*outp++ = toX + 'a';
*outp++ = toY + '1';
*outp++ = ToUpper(promoChar);
*outp = NULLCHAR;
return BadMove;
}
else
return ret;
default:
/* Illegal move; use coordinate notation */
outp = out;
*outp++ = fromX + 'a';
*outp++ = fromY + '1';
*outp++ = toX + 'a';
*outp++ = toY + '1';
*outp++ = ToUpper(promoChar);
*outp = NULLCHAR;
return BadMove;
}
}
void InitPosition(redraw)
int redraw;
{
currentMove = forwardMostMove = backwardMostMove = 0;
CopyBoard(boards[0], initialPosition);
if (redraw)
DrawPosition(FALSE, boards[currentMove]);
}
void CopyBoard(to, from)
Board to, from;
{
int i, j;
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE; j++)
to[i][j] = from[i][j];
}
Boolean CompareBoards(board1, board2)
Board board1, board2;
{
int i, j;
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE; j++) {
if (board1[i][j] != board2[i][j])
return FALSE;
}
return TRUE;
}
void SendCurrentBoard(pr)
ProcRef pr;
{
SendBoard(pr, boards[currentMove]);
}
void SendBoard(pr, board)
ProcRef pr;
Board board;
{
char message[MSG_SIZ];
ChessSquare *bp;
int i, j;
SendToProgram("edit\n", pr);
SendToProgram("#\n", pr);
for (i = BOARD_SIZE - 1; i >= 0; i--) {
bp = &board[i][0];
for (j = 0; j < BOARD_SIZE; j++, bp++) {
if ((int) *bp < (int) BlackPawn) {
sprintf(message, "%c%c%c\n", PieceToChar(*bp),
'a' + j, '1' + i);
SendToProgram(message, pr);
}
}
}
SendToProgram("c\n", pr);
for (i = BOARD_SIZE - 1; i >= 0; i--) {
bp = &board[i][0];
for (j = 0; j < BOARD_SIZE; j++, bp++) {
if (((int) *bp != (int) EmptySquare)
&& ((int) *bp >= (int) BlackPawn)) {
sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
'a' + j, '1' + i);
SendToProgram(message, pr);
}
}
}
SendToProgram(".\n", pr);
}
int IsPromotion(fromX, fromY, toX, toY)
int fromX, fromY, toX, toY;
{
return gameMode != EditPosition &&
fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
(boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
}
int OKToStartUserMove(x, y)
int x, y;
{
ChessSquare from_piece;
int white_piece;
if (matchMode) return FALSE;
if (x >= 0 && y >= 0)
from_piece = boards[currentMove][y][x];
else
from_piece = EmptySquare;
if (gameMode == EditPosition) return TRUE;
if (from_piece == EmptySquare) return FALSE;
white_piece = (int)from_piece >= (int)WhitePawn &&
(int)from_piece <= (int)WhiteKing;
switch (gameMode) {
case EndOfGame:
case PlayFromGameFile:
case TwoMachinesPlay:
return FALSE;
case IcsObserving:
case IcsIdle:
return FALSE;
case MachinePlaysWhite:
case IcsPlayingBlack:
if (white_piece) {
DisplayError("You are playing Black", 0);
return FALSE;
}
break;
case IcsPlayingWhite:
case MachinePlaysBlack:
if (!white_piece) {
DisplayError("You are playing White", 0);
return FALSE;
}
break;
case ForceMoves:
/*!!Prompt for confirmation if discarding moves?*/
forwardMostMove = currentMove;
break;
case BeginningOfGame:
if (appData.icsActive) return FALSE;
if (!appData.noChessProgram) {
if (!white_piece) {
DisplayError("You are playing White", 0);
return FALSE;
}
}
break;
default:
break;
}
if (currentMove != forwardMostMove) {
DisplayError("Displayed position is not current", 0);
return FALSE;
}
return TRUE;
}
void UserMoveEvent(fromX, fromY, toX, toY, promoChar)
int fromX, fromY, toX, toY;
int promoChar;
{
ChessMove moveType;
char user_move[MSG_SIZ];
if (fromX < 0 || fromY < 0) return;
if ((fromX == toX) && (fromY == toY)) {
return;
}
if (toX < 0 || toY < 0) {
if (gameMode == EditPosition && (toX == -2 || toY == -2)) {
boards[0][fromY][fromX] = EmptySquare;
DrawPosition(FALSE, boards[currentMove]);
}
return;
}
if (gameMode == EditPosition) {
boards[0][toY][toX] = boards[0][fromY][fromX];
boards[0][fromY][fromX] = EmptySquare;
DrawPosition(FALSE, boards[currentMove]);
return;
}
/* Check if the user is playing in turn. This is complicated because we
let the user "pick up" a piece before it is his turn. So the piece he
tried to pick up may have been captured by the time he puts it down!
Therefore we use the color the user is supposed to be playing in this
test, not the color of the piece that is currently on the starting
square---except in ForceMoves mode, where the user is playing both
sides; fortunately there the capture race can't happen.
*/
if (gameMode == MachinePlaysWhite || gameMode == IcsPlayingBlack ||
((gameMode == ForceMoves || gameMode == BeginningOfGame) &&
(int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
(int) boards[currentMove][fromY][fromX] <= (int) BlackKing)) {
/* User is moving for Black */
if (WhiteOnMove(currentMove)) {
DisplayError("It is White's turn", 0);
return;
}
} else {
/* User is moving for White */
if (!WhiteOnMove(currentMove)) {
DisplayError("It is Black's turn", 0);
return;
}
}
moveType = LegalityTest(WhiteOnMove(currentMove),
boards[currentMove], fromY, fromX,
toY, toX, promoChar);
if (moveType == BadMove) {
DisplayError("Illegal move", 0);
return;
}
/* A user move restarts a paused game*/
if (pausing)
PauseEvent();
MakeMove(&moveType, fromX, fromY, toX, toY);
if (appData.icsActive) {
if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) {
sprintf(user_move, "%c%c%c%c\n",
'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
switch (moveType) {
default:
DisplayError("Internal error; bad moveType", 0);
break;
case WhiteKingSideCastle:
case BlackKingSideCastle:
case WhiteQueenSideCastleWild:
case BlackQueenSideCastleWild:
sprintf(user_move, "o-o\n");
break;
case WhiteQueenSideCastle:
case BlackQueenSideCastle:
case WhiteKingSideCastleWild:
case BlackKingSideCastleWild:
sprintf(user_move, "o-o-o\n");
break;
case WhitePromotionQueen:
case BlackPromotionQueen:
SendToICS("promote queen\n");
break;
case WhitePromotionRook:
case BlackPromotionRook:
SendToICS("promote rook\n");
break;
case WhitePromotionBishop:
case BlackPromotionBishop:
SendToICS("promote bishop\n");
break;
case WhitePromotionKnight:
case BlackPromotionKnight:
SendToICS("promote knight\n");
break;
case NormalMove:
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
break;
}
SendToICS(user_move);
ics_user_moved = 1;
}
} else {
promoChar = ToLower(PieceToChar(PromoPiece(moveType)));
if (promoChar == '.')
sprintf(user_move, "%c%c%c%c\n",
'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
else
sprintf(user_move, "%c%c%c%c%c\n",
'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
promoChar);
Attention(firstProgramPR);
if (firstSendTime)
SendTimeRemaining(firstProgramPR);
SendToProgram(user_move, firstProgramPR);
}
strcpy(moveList[currentMove - 1], user_move);
switch (gameMode) {
case PlayFromGameFile:
ForceMovesEvent();
break;
case BeginningOfGame:
if (appData.noChessProgram)
lastGameMode = gameMode = ForceMoves;
else
lastGameMode = gameMode = MachinePlaysBlack;
ModeHighlight();
break;
case ForceMoves:
case MachinePlaysBlack:
case MachinePlaysWhite:
default:
break;
}
}
/* Parser for moves from gnuchess or ICS */
Boolean ParseMachineMove(machineMove, moveNum,
moveType, fromX, fromY, toX, toY, promoChar)
char *machineMove;
int moveNum;
ChessMove *moveType;
int *fromX, *fromY, *toX, *toY;
char *promoChar;
{
char buf[MSG_SIZ];
*moveType = yylexstr(moveNum, machineMove);
switch (*moveType) {
case WhitePromotionQueen:
case BlackPromotionQueen:
case WhitePromotionRook:
case BlackPromotionRook:
case WhitePromotionBishop:
case BlackPromotionBishop:
case WhitePromotionKnight:
case BlackPromotionKnight:
case NormalMove:
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
case WhiteKingSideCastle:
case WhiteQueenSideCastle:
case BlackKingSideCastle:
case BlackQueenSideCastle:
case WhiteKingSideCastleWild:
case WhiteQueenSideCastleWild:
case BlackKingSideCastleWild:
case BlackQueenSideCastleWild:
*fromX = currentMoveString[0] - 'a';
*fromY = currentMoveString[1] - '1';
*toX = currentMoveString[2] - 'a';
*toY = currentMoveString[3] - '1';
*promoChar = currentMoveString[4];
return TRUE;
case BadMove:
case AmbiguousMove:
case (ChessMove) 0: /* end of file */
case ElapsedTime:
case Comment:
case WhiteWins:
case BlackWins:
case GameIsDrawn:
default:
/* bug? */
sprintf(buf, "Bad move in chess program output: %s",
machineMove);
DisplayError(buf, 0);
*fromX = *fromY = *toX = *toY = 0;
return FALSE;
}
}
void HandleMachineMove(message, isr)
char *message;
InputSourceRef isr;
{
char machineMove[MSG_SIZ], buf1[MSG_SIZ], buf2[MSG_SIZ];
int fromX, fromY, toX, toY;
ChessMove moveType;
char promoChar;
maybeThinking = FALSE;
if (strncmp(message, "warning:", 8) == 0) {
DisplayError(message, 0);
return;
}
/*
* If chess program startup fails, exit with an error message.
* Attempts to recover here are futile.
*/
if ((StrStr(message, "unknown host") != NULL)
|| (StrStr(message, "No remote directory") != NULL)
|| (StrStr(message, "not found") != NULL)
|| (StrStr(message, "No such file") != NULL)
|| (StrStr(message, "Permission denied") != NULL)) {
sprintf(buf1,
"Failed to start chess program %s on %s: %s\n",
isr == firstProgramISR ? appData.firstChessProgram
: appData.secondChessProgram,
isr == firstProgramISR ? appData.firstHost
: appData.secondHost,
message);
DisplayFatalError(buf1, 0);
ExitEvent(1);
}
if (strncmp(message, "time", 4) == 0) {
if (StrStr(message, "CHESS")) {
/* Program has a broken "time" command that
outputs a string not ending in newline.
Don't use it. */
if (isr == firstProgramISR) firstSendTime = 0;
if (isr == secondProgramISR) secondSendTime = 0;
} else {
if (isr == firstProgramISR) firstSendTime = 1;
if (isr == secondProgramISR) secondSendTime = 1;
}
}
/*
* If the move is illegal, cancel it and redraw the board.
*/
if (strncmp(message, "Illegal move", 12) == 0) {
if (isr == firstProgramISR && firstSendTime == 2) {
/* First program doesn't have the "time" command */
firstSendTime = 0;
return;
} else if (isr == secondProgramISR && secondSendTime == 2) {
/* Second program doesn't have the "time" command */
secondSendTime = 0;
return;
}
if (forwardMostMove <= backwardMostMove) return;
if (pausing) PauseEvent();
if (gameMode == PlayFromGameFile) {
/* Stop reading this game file */
gameMode = ForceMoves;
ModeHighlight();
}
currentMove = --forwardMostMove;
SwitchClocks();
sprintf(buf1, "Illegal move: %s", parseList[currentMove]);
DisplayError(buf1, 0);
DrawPosition(FALSE, boards[currentMove]);
return;
}
if (strncmp(message, "Hint:", 5) == 0) {
sscanf(message, "Hint: %s", machineMove);
if (ParseMachineMove(machineMove, forwardMostMove, &moveType,
&fromX, &fromY, &toX, &toY, &promoChar)) {
moveType = CoordsToAlgebraic(fromX, fromY, toX, toY, promoChar,
forwardMostMove, buf1);
sprintf(buf2, "Hint: %s", buf1);
DisplayMessage(buf2);
} else {
/* Hint move was illegal!? */
DisplayMessage(message);
}
return;
}
/*
* win, lose or draw
*/
if (strncmp(message, "White", 5) == 0) {
ShutdownChessPrograms("White mates");
return;
} else if (strncmp(message, "Black", 5) == 0) {
ShutdownChessPrograms("Black mates");
return;
} else if (strncmp(message, "opponent mates!", 15) == 0) {
switch (gameMode) {
case MachinePlaysBlack:
ShutdownChessPrograms("White mates");
break;
case MachinePlaysWhite:
ShutdownChessPrograms("Black mates");
break;
case TwoMachinesPlay:
ShutdownChessPrograms(isr == firstProgramISR ?
"White mates" : "Black mates");
break;
default:
/* can't happen */
break;
}
return;
} else if (strncmp(message, "computer mates!", 15) == 0) {
switch (gameMode) {
case MachinePlaysBlack:
ShutdownChessPrograms("Black mates");
break;
case MachinePlaysWhite:
ShutdownChessPrograms("White mates");
break;
case TwoMachinesPlay:
ShutdownChessPrograms(isr == firstProgramISR ?
"Black mates" : "White mates");
break;
default:
/* can't happen */
break;
}
return;
} else if (strncmp(message, "Draw", 4) == 0) {
ShutdownChessPrograms("Draw");
return;
}
/*
* normal machine reply move
*/
maybeThinking = TRUE;
if (StrStr(message, "...") != NULL) {
sscanf(message, "%s %s %s", buf1, buf2, machineMove);
if (machineMove[0] == NULLCHAR)
return;
} else
return; /* ignore noise */
strcat(machineMove, "\n");
strcpy(moveList[forwardMostMove], machineMove);
if (!ParseMachineMove(machineMove, forwardMostMove, &moveType,
&fromX, &fromY, &toX, &toY, &promoChar)) {
/* Machine move was illegal!? What to do? */
DisplayError("Ignoring illegal move from machine", 0);
return;
}
if (!pausing)
currentMove = forwardMostMove; /*display latest move*/
MakeMove(&moveType, fromX, fromY, toX, toY);
if (!pausing && appData.ringBellAfterMoves)
RingBell();
if (gameMode == TwoMachinesPlay) {
if (WhiteOnMove(forwardMostMove)) {
Attention(secondProgramPR);
if (secondSendTime)
SendTimeRemaining(secondProgramPR);
SendToProgram(machineMove, secondProgramPR);
if (firstMove) {
firstMove = FALSE;
SendToProgram(appData.whiteString,
secondProgramPR);
}
} else {
Attention(firstProgramPR);
if (firstSendTime)
SendTimeRemaining(firstProgramPR);
SendToProgram(machineMove, firstProgramPR);
if (firstMove) {
firstMove = FALSE;
SendToProgram(appData.blackString,
firstProgramPR);
}
}
}
}
/* Parse a game score from the character string "game", and
record it as the history of the current game. The game
score is NOT assumed to start from the standard position.
The display is not updated in any way.
*/
void ParseGameHistory(game)
char *game;
{
ChessMove moveType;
int fromX, fromY, toX, toY, boardIndex;
char promoChar;
char *p;
char buf[MSG_SIZ];
if (appData.debugMode)
fprintf(debugFP, "Parsing game history: %s\n", game);
/* Parse out names of players */
while (*game == ' ') game++;
p = ics_white;
while (*game != ' ') *p++ = *game++;
*p = NULLCHAR;
while (*game == ' ') game++;
p = ics_black;
while (*game != ' ' && *game != '\n') *p++ = *game++;
*p = NULLCHAR;
/* Parse moves */
boardIndex = 0;
yynewstr(game);
for (;;) {
yyboardindex = boardIndex;
moveType = (ChessMove) yylex();
switch (moveType) {
case WhitePromotionQueen:
case BlackPromotionQueen:
case WhitePromotionRook:
case BlackPromotionRook:
case WhitePromotionBishop:
case BlackPromotionBishop:
case WhitePromotionKnight:
case BlackPromotionKnight:
case NormalMove:
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
case WhiteKingSideCastle:
case WhiteQueenSideCastle:
case BlackKingSideCastle:
case BlackQueenSideCastle:
case WhiteKingSideCastleWild:
case WhiteQueenSideCastleWild:
case BlackKingSideCastleWild:
case BlackQueenSideCastleWild:
fromX = currentMoveString[0] - 'a';
fromY = currentMoveString[1] - '1';
toX = currentMoveString[2] - 'a';
toY = currentMoveString[3] - '1';
promoChar = currentMoveString[4];
break;
case BadMove:
case AmbiguousMove:
/* bug? */
sprintf(buf, "Bad move in ICS output: %s", yytext);
DisplayError(buf, 0);
/* fall thru */
case (ChessMove) 0: /* end of file */
if (boardIndex < backwardMostMove) {
/* Oops, gap. How did that happen? */
return;
}
backwardMostMove = 0;
if (boardIndex > forwardMostMove) {
forwardMostMove = boardIndex;
}
return;
case ElapsedTime:
if (boardIndex > 0) {
strcat(parseList[boardIndex-1], " ");
strcat(parseList[boardIndex-1], yytext);
}
continue;
case Comment:
default:
/* ignore */
continue;
case WhiteWins:
case BlackWins:
case GameIsDrawn:
strncpy(endMessage, yytext, MOVE_LEN * 4);
endMessage[MOVE_LEN * 4 - 1] = NULLCHAR;
continue;
}
(void) CoordsToAlgebraic(fromX, fromY, toX, toY, promoChar,
boardIndex, parseList[boardIndex]);
CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
boardIndex++;
ApplyMove(&moveType, fromX, fromY, toX, toY,
boards[boardIndex]);
}
}
void LoadGameLoop()
{
for (;;) {
if (!LoadGameOneMove())
return;
if (matchMode || appData.timeDelay == 0)
continue;
if (appData.timeDelay < 0)
return;
StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
break;
}
}
int LoadGameOneMove()
{
int fromX, fromY, toX, toY, done;
ChessMove moveType;
char move[MSG_SIZ];
if (gameFileFP == NULL)
return FALSE;
if (gameMode != PlayFromGameFile) {
gameFileFP = NULL;
return FALSE;
}
yyboardindex = forwardMostMove;
moveType = (ChessMove) yylex();
done = FALSE;
switch (moveType) {
case Comment:
if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yytext);
if (!matchMode && (pausing || appData.timeDelay != 0)) {
CommentPopUp(yytext);
}
AppendComment(currentMove, yytext);
return TRUE;
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
case WhitePromotionQueen:
case BlackPromotionQueen:
case WhitePromotionRook:
case BlackPromotionRook:
case WhitePromotionBishop:
case BlackPromotionBishop:
case WhitePromotionKnight:
case BlackPromotionKnight:
case NormalMove:
case WhiteKingSideCastle:
case WhiteQueenSideCastle:
case BlackKingSideCastle:
case BlackQueenSideCastle:
case WhiteKingSideCastleWild:
case WhiteQueenSideCastleWild:
case BlackKingSideCastleWild:
case BlackQueenSideCastleWild:
if (appData.debugMode)
fprintf(debugFP, "Parsed %s into %s\n", yytext, currentMoveString);
fromX = currentMoveString[0] - 'a';
fromY = currentMoveString[1] - '1';
toX = currentMoveString[2] - 'a';
toY = currentMoveString[3] - '1';
break;
case (ChessMove) 0: /* end of file */
if (appData.debugMode)
fprintf(debugFP, "Parser hit end of file\n");
DisplayMessage("End of game file");
done = TRUE;
break;
case WhiteWins:
case BlackWins:
case GameIsDrawn:
if (appData.debugMode)
fprintf(debugFP, "Parsed game end: %s\n", yytext);
if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
DrawPosition(FALSE, boards[currentMove]);
if (!appData.matchMode && commentList[currentMove] != NULL)
CommentPopUp(commentList[currentMove]);
}
GameEnds(yytext);
return FALSE;
case XBoardGame:
case GNUChessGame:
/* Reached start of next game in file */
if (appData.debugMode)
fprintf(debugFP, "Parsed start of next game: %s\n", yytext);
DisplayMessage("End of game");
done = TRUE;
break;
case PositionDiagram:
/* should not happen; ignore */
case MoveNumberOne:
/* ignore; we see too many bogus move numbers,
especially in GNUChess game files */
case ElapsedTime:
/* ignore */
if (appData.debugMode)
fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
yytext, (int) moveType);
return LoadGameOneMove(); /* tail recursion */
default:
case BadMove:
if (appData.debugMode)
fprintf(debugFP, "Parsed BadMove: %s\n", yytext);
sprintf(move, "Bad move: %d. %s%s",
(forwardMostMove / 2) + 1,
WhiteOnMove(forwardMostMove) ? "" : "... ", yytext);
DisplayError(move, 0);
done = TRUE;
break;
case AmbiguousMove:
if (appData.debugMode)
fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yytext);
sprintf(move, "Ambiguous move: %d. %s%s",
(forwardMostMove / 2) + 1,
WhiteOnMove(forwardMostMove) ? "" : "... ", yytext);
DisplayError(move, 0);
done = TRUE;
break;
}
if (done) {
if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
DrawPosition(FALSE, boards[currentMove]);
if (!appData.matchMode && commentList[currentMove] != NULL)
CommentPopUp(commentList[currentMove]);
}
lastGameMode = gameMode;
gameMode = ForceMoves;
ModeHighlight();
(void) StopLoadGameTimer();
gameFileFP = NULL;
return FALSE;
} else {
strcat(currentMoveString, "\n");
strcpy(moveList[forwardMostMove], currentMoveString);
SendToProgram(currentMoveString, firstProgramPR);
MakeMove(&moveType, fromX, fromY, toX, toY);
return TRUE;
}
}
/* Apply a move to the given board. Oddity: moveType is ignored on
input unless the move is seen to be a pawn promotion, in which case
moveType tells us what to promote to.
*/
void ApplyMove(moveType, fromX, fromY, toX, toY, board)
ChessMove *moveType;
int fromX, fromY, toX, toY;
Board board;
{
if (fromY == 0 && fromX == 4
&& board[fromY][fromX] == WhiteKing
&& toY == 0 && toX == 6) {
*moveType = WhiteKingSideCastle;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = WhiteKing;
board[fromY][7] = EmptySquare;
board[toY][5] = WhiteRook;
} else if (fromY == 0 && fromX == 4
&& board[fromY][fromX] == WhiteKing
&& toY == 0 && toX == 2) {
*moveType = WhiteQueenSideCastle;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = WhiteKing;
board[fromY][0] = EmptySquare;
board[toY][3] = WhiteRook;
} else if (fromY == 0 && fromX == 3
&& board[fromY][fromX] == WhiteKing
&& toY == 0 && toX == 5) {
*moveType = WhiteKingSideCastleWild;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = WhiteKing;
board[fromY][7] = EmptySquare;
board[toY][4] = WhiteRook;
} else if (fromY == 0 && fromX == 3
&& board[fromY][fromX] == WhiteKing
&& toY == 0 && toX == 1) {
*moveType = WhiteQueenSideCastleWild;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = WhiteKing;
board[fromY][0] = EmptySquare;
board[toY][2] = WhiteRook;
} else if (fromY == 6
&& board[fromY][fromX] == WhitePawn
&& toY == 7) {
/* white pawn promotion */
board[7][toX] = PromoPiece(*moveType);
if (board[7][toX] == EmptySquare) {
board[7][toX] = WhiteQueen;
*moveType = WhitePromotionQueen;
}
board[6][fromX] = EmptySquare;
} else if ((fromY == 4)
&& (toX != fromX)
&& (board[fromY][fromX] == WhitePawn)
&& (board[toY][toX] == EmptySquare)) {
*moveType = WhiteCapturesEnPassant;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = WhitePawn;
board[toY - 1][toX] = EmptySquare;
} else if (fromY == 7 && fromX == 4
&& board[fromY][fromX] == BlackKing
&& toY == 7 && toX == 6) {
*moveType = BlackKingSideCastle;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = BlackKing;
board[fromY][7] = EmptySquare;
board[toY][5] = BlackRook;
} else if (fromY == 7 && fromX == 4
&& board[fromY][fromX] == BlackKing
&& toY == 7 && toX == 2) {
*moveType = BlackQueenSideCastle;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = BlackKing;
board[fromY][0] = EmptySquare;
board[toY][3] = BlackRook;
} else if (fromY == 7 && fromX == 3
&& board[fromY][fromX] == BlackKing
&& toY == 7 && toX == 5) {
*moveType = BlackKingSideCastleWild;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = BlackKing;
board[fromY][7] = EmptySquare;
board[toY][4] = BlackRook;
} else if (fromY == 7 && fromX == 3
&& board[fromY][fromX] == BlackKing
&& toY == 7 && toX == 1) {
*moveType = BlackQueenSideCastleWild;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = BlackKing;
board[fromY][0] = EmptySquare;
board[toY][2] = BlackRook;
} else if (fromY == 1
&& board[fromY][fromX] == BlackPawn
&& toY == 0) {
/* black pawn promotion */
board[0][toX] = PromoPiece(*moveType);
if (board[0][toX] == EmptySquare) {
board[0][toX] = BlackQueen;
*moveType = BlackPromotionQueen;
}
board[1][fromX] = EmptySquare;
} else if ((fromY == 3)
&& (toX != fromX)
&& (board[fromY][fromX] == BlackPawn)
&& (board[toY][toX] == EmptySquare)) {
*moveType = BlackCapturesEnPassant;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = BlackPawn;
board[toY + 1][toX] = EmptySquare;
} else {
*moveType = NormalMove;
board[toY][toX] = board[fromY][fromX];
board[fromY][fromX] = EmptySquare;
}
}
/*
* MakeMove() displays moves. If they are illegal, GNU chess will detect
* this and send an Illegal move message. XBoard will then retract the move.
*
* Oddity: moveType is ignored on input unless the move is seen to be a
* pawn promotion, in which case moveType tells us what to promote to.
*/
void MakeMove(moveType, fromX, fromY, toX, toY)
ChessMove *moveType;
int fromX, fromY, toX, toY;
{
CommentPopDown();
forwardMostMove++;
if (commentList[forwardMostMove] != NULL) {
free(commentList[forwardMostMove]);
commentList[forwardMostMove] = NULL;
}
CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
ApplyMove(moveType, fromX, fromY, toX, toY,
boards[forwardMostMove]);
endMessage[0] = NULLCHAR;
(void) CoordsToAlgebraic(fromX, fromY, toX, toY,
ToLower(PieceToChar(PromoPiece(*moveType))),
forwardMostMove - 1,
parseList[forwardMostMove - 1]);
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
if (pausing && gameMode != PlayFromGameFile) return;
currentMove = forwardMostMove;
if ((gameMode == PlayFromGameFile) &&
(matchMode || (appData.timeDelay == 0 && !pausing))) return;
SwitchClocks();
DisplayMove(currentMove - 1);
DrawPosition(FALSE, boards[currentMove]);
}
void InitChessProgram(hostName, programName, pr, isr, sendTime)
char *hostName, *programName;
ProcRef *pr;
InputSourceRef *isr;
int *sendTime;
{
char cmdLine[MSG_SIZ];
char buf[MSG_SIZ];
int err;
if (appData.noChessProgram) return;
if (strcmp(hostName, "localhost") == 0) {
if (*appData.searchTime != NULLCHAR) {
sprintf(cmdLine, "%s %d", programName, searchTime);
} else if (appData.searchDepth > 0) {
sprintf(cmdLine, "%s 1 9999", programName);
} else {
sprintf(cmdLine, "%s %d %s", programName,
appData.movesPerSession, appData.timeControl);
}
} else {
if (*appData.searchTime != NULLCHAR) {
sprintf(cmdLine, "%s %s %s %d",
appData.remoteShell, hostName, programName, searchTime);
} else if (appData.searchDepth > 0) {
sprintf(cmdLine, "%s %s %s 1 9999",
appData.remoteShell, hostName, programName);
} else {
sprintf(cmdLine, "%s %s %s %d %s",
appData.remoteShell, hostName, programName,
appData.movesPerSession, appData.timeControl);
}
}
err = StartChildProcess(cmdLine, pr);
if (err != 0) {
sprintf(buf, "Startup failure on '%s'", programName);
DisplayFatalError(buf, err);
*pr = NoProc;
return; /* don't actually exit */
}
#ifdef NOTDEF
/*!!*/
/* Read "Chess" */
if (fgets(buf, MSG_SIZ, *from) == NULL) {
sprintf(buf, "No response from '%s'", programName);
DisplayFatalError(buf, errno);
DestroyChildProcess(*pr);
*pr = NULL;
}
#endif
*isr = AddInputSource(*pr, TRUE, ReceiveFromProgram);
SendToProgram(appData.initString, *pr);
SendSearchDepth(*pr);
if (*sendTime == 2) {
/* Does program have "time" command? */
char buf[MSG_SIZ];
sprintf(buf, "time %d\nhelp\n", blackTimeRemaining/10);
/* "help" is a kludge to work around a gnuchess bug;
some versions do not send a newline at the end of
their response to the time command */
SendToProgram(buf, *pr);
}
}
void GameEnds(why)
char *why;
{
GameMode gm;
gm = gameMode;
if (appData.icsActive) {
gameMode = IcsIdle;
ics_gamenum = -1;
ics_user_moved = FALSE;
}
(void) StopLoadGameTimer();
if (gameFileFP != NULL) {
gameFileFP = NULL;
}
StopClocks();
if (why == NULL) return;
strncpy(endMessage, why, MOVE_LEN * 4);
endMessage[MOVE_LEN * 4 - 1] = NULLCHAR;
if (currentMove == forwardMostMove)
DisplayMove(currentMove - 1);
if (forwardMostMove == 0) return;
if (gm != PlayFromGameFile) {
if (*appData.saveGameFile != NULLCHAR) {
SaveGameToFile(appData.saveGameFile);
} else if (appData.autoSaveGames) {
AutoSaveGame();
}
if (*appData.savePositionFile != NULLCHAR) {
SavePositionToFile(appData.savePositionFile);
}
}
}
void ShutdownChessPrograms(why)
char *why;
{
char *quit = "quit\n";
int dummy;
GameEnds(why);
if (appData.icsActive) return;
lastGameMode = gameMode;
gameMode = EndOfGame;
ModeHighlight();
if (firstProgramISR != NULL)
RemoveInputSource(firstProgramISR);
firstProgramISR = NULL;
if (firstProgramPR != NoProc) {
OutputToProcess(firstProgramPR, quit, strlen(quit), &dummy);
DestroyChildProcess(firstProgramPR);
}
firstProgramPR = NoProc;
if (secondProgramISR != NULL)
RemoveInputSource(secondProgramISR);
secondProgramISR = NULL;
if (secondProgramPR != NoProc) {
OutputToProcess(secondProgramPR, quit, strlen(quit), &dummy);
DestroyChildProcess(secondProgramPR);
}
secondProgramPR = NoProc;
if (matchMode) {
exit(0);
}
}
/*
* Button procedures
*/
void ExitEvent(status)
int status;
{
/* Save game if resource set and not already saved */
if (*endMessage == NULLCHAR && forwardMostMove > 0) {
if (*appData.saveGameFile != NULLCHAR) {
SaveGameToFile(appData.saveGameFile);
} else if (appData.autoSaveGames) {
AutoSaveGame();
}
if (*appData.savePositionFile != NULLCHAR) {
SavePositionToFile(appData.savePositionFile);
}
}
ShutdownChessPrograms(NULL);
if (icsPR != NoProc) {
DestroyChildProcess(icsPR);
}
exit(status);
}
void CallFlagEvent()
{
/* Call your opponent's flag (claim a win on time) */
if (appData.icsActive) {
SendToICS("flag\n");
} else {
switch (gameMode) {
default:
return;
case MachinePlaysWhite:
if (whiteFlag) {
if (blackFlag)
ShutdownChessPrograms("Draw (both players ran out of time)");
else
ShutdownChessPrograms("Black wins on time");
}
break;
case MachinePlaysBlack:
if (blackFlag) {
if (whiteFlag)
ShutdownChessPrograms("Draw (both players ran out of time)");
else
ShutdownChessPrograms("White wins on time");
}
break;
}
}
}
void DrawEvent()
{
/* Offer draw or accept pending draw offer from opponent */
if (appData.icsActive) {
/* Note: tournament rules require draw offers to be
made after you make your move but before you punch
your clock. Currently ICS doesn't let you do that;
instead, you always punch your clock after making a
move, but you can offer a draw at any time. */
SendToICS("draw\n");
} else {
/* Currently GNU Chess doesn't offer or accept draws
at all, so there is no Draw button in GNU Chess
mode. */
DisplayError("Function not implemented", 0);
}
}
void DeclineDrawEvent()
{
/* Decline a pending draw offer from opponent */
if (appData.icsActive) {
/* Note: ICS also lets you withdraw your own draw
offer with this command. I'm not sure how long
your draw offer remains pending if you don't
withdraw it. */
SendToICS("decline draw\n");
} else {
/* Currently GNU Chess doesn't offer or accept draws
at all, so there is no Decline Draw button in
GNU Chess mode. */
DisplayError("Function not implemented", 0);
}
}
void AdjournEvent()
{
/* Offer Adjourn or accept pending Adjourn offer from opponent */
if (appData.icsActive) {
SendToICS("adjourn\n");
} else {
/* Currently GNU Chess doesn't offer or accept Adjourns
at all, so there is no Adjourn button in GNU Chess
mode. */
DisplayError("Function not implemented", 0);
}
}
void DeclineAdjournEvent()
{
/* Decline a pending Adjourn offer from opponent */
if (appData.icsActive) {
/* Note: ICS also lets you withdraw your own Adjourn
offer with this command. I'm not sure how long
your Adjourn offer remains pending if you don't
withdraw it. */
SendToICS("decline adjourn\n");
} else {
/* Currently GNU Chess doesn't offer or accept Adjourns
at all, so there is no Decline Adjourn button in
GNU Chess mode. */
DisplayError("Function not implemented", 0);
}
}
void AbortEvent()
{
/* Offer Abort or accept pending Abort offer from opponent */
if (appData.icsActive) {
SendToICS("abort\n");
} else {
DisplayError("Function not implemented", 0);
}
}
void DeclineAbortEvent()
{
/* Decline a pending Abort offer from opponent */
if (appData.icsActive) {
/* Note: ICS also lets you withdraw your own Abort
offer with this command. I'm not sure how long
your Abort offer remains pending if you don't
withdraw it. */
SendToICS("decline abort\n");
} else {
/* Currently GNU Chess doesn't offer or accept Aborts
at all, so there is no Decline Abort button in
GNU Chess mode. */
DisplayError("Function not implemented", 0);
}
}
void ResignEvent()
{
/* Resign. You can do this even if it's not your turn. */
if (appData.icsActive) {
SendToICS("resign\n");
} else {
switch (gameMode) {
case MachinePlaysWhite:
ShutdownChessPrograms("Black resigns");
break;
case MachinePlaysBlack:
ShutdownChessPrograms("White resigns");
break;
default:
break;
}
}
}
int LoadPositionFromFile(filename, n)
char *filename;
int n;
{
FILE *f;
char buf[MSG_SIZ];
int ret;
f = fopen(filename, "r");
if (f == NULL) {
sprintf(buf, "Can't open %s", filename);
DisplayError(buf, errno);
return FALSE;
} else {
ret = LoadPosition(f, n);
return ret;
}
}
int LoadPosition(fp, positionNumber)
FILE *fp;
int positionNumber;
{
char *p, line[MSG_SIZ];
Board initial_position;
int i, j;
if (gameMode != BeginningOfGame) {
Reset(TRUE);
}
lastGameMode = gameMode = ForceMoves;
ModeHighlight();
startedFromSetupPosition = TRUE;
if (firstProgramPR == NoProc)
InitChessProgram(appData.firstHost, appData.firstChessProgram,
&firstProgramPR, &firstProgramISR, &firstSendTime);
if (positionNumber < 2) {
/* Backward compatibility---don't look for '#' */
(void) fgets(line, MSG_SIZ, fp);
} else {
while (positionNumber > 0) {
/* skip postions before number positionNumber */
if (fgets(line, MSG_SIZ, fp) == NULL) {
Reset(TRUE);
DisplayError("Position not found in file", 0);
return FALSE;
}
if (line[0] == '#') positionNumber--;
}
}
(void) fgets(line, MSG_SIZ, fp);
(void) fgets(line, MSG_SIZ, fp);
for (i = BOARD_SIZE - 1; i >= 0; i--) {
(void) fgets(line, MSG_SIZ, fp);
for (p = line, j = 0; j < BOARD_SIZE; p++) {
if (*p == ' ')
continue;
initial_position[i][j++] = CharToPiece(*p);
}
}
blackPlaysFirst = FALSE;
if (!feof(fp)) {
(void) fgets(line, MSG_SIZ, fp);
if (strncmp(line, "black", strlen("black"))==0)
blackPlaysFirst = TRUE;
}
fclose(fp);
if (blackPlaysFirst) {
CopyBoard(boards[0], initial_position);
strcpy(moveList[0], "");
strcpy(parseList[0], "");
currentMove = forwardMostMove = backwardMostMove = 1;
CopyBoard(boards[1], initial_position);
SendToProgram("force\na3\n", firstProgramPR);
SendCurrentBoard(firstProgramPR);
DisplayMessage("Black to play");
} else {
currentMove = forwardMostMove = backwardMostMove = 0;
CopyBoard(boards[0], initial_position);
SendCurrentBoard(firstProgramPR);
SendToProgram("force\n", firstProgramPR);
DisplayMessage("White to play");
}
ResetClocks();
timeRemaining[0][1] = whiteTimeRemaining;
timeRemaining[1][1] = blackTimeRemaining;
DrawPosition(FALSE, boards[currentMove]);
return TRUE;
}
int LoadGameFromFile(filename, n)
char *filename;
int n;
{
FILE *f;
char buf[MSG_SIZ];
int ret;
f = fopen(filename, "r");
if (f == NULL) {
sprintf(buf, "Can't open %s", filename);
DisplayError(buf, errno);
return FALSE;
} else {
ret = LoadGame(f, n);
return ret;
}
}
FILE *lastLoadGameFP = NULL;
int lastLoadGameNumber = 0;
int ReloadGame(offset)
int offset;
{
if (lastLoadGameFP == NULL) {
DisplayError("No game has been loaded", 0);
return FALSE;
}
rewind(lastLoadGameFP);
return LoadGame(lastLoadGameFP, lastLoadGameNumber + offset);
}
int LoadGame(f, gameNumber)
FILE *f;
int gameNumber;
{
ChessMove cm, lastStart;
if (gameMode != BeginningOfGame) {
Reset(FALSE);
}
yynewfile(f);
gameFileFP = f;
if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
fclose(lastLoadGameFP);
}
lastLoadGameFP = f;
lastLoadGameNumber = gameNumber;
lastGameMode = gameMode = PlayFromGameFile;
ModeHighlight();
InitPosition(FALSE);
StopClocks();
if (firstProgramPR == NoProc) {
InitChessProgram(appData.firstHost, appData.firstChessProgram,
&firstProgramPR, &firstProgramISR, &firstSendTime);
} else {
SendToProgram(appData.initString, firstProgramPR);
SendSearchDepth(firstProgramPR);
}
SendToProgram("force\n", firstProgramPR);
/*
* Skip the first gameNumber-1 games in the file.
* Also skip over anything that precedes an identifiable
* start of game marker, to avoid being confused by
* garbage at the start of the file. Currently
* recognized start of game markers are the move number "1",
* the pattern "gnuchess .* game", and the pattern
* "^# [^ ]* game file". A game that starts with the
* latter pattern will also have a move number 1, possibly
* following a position diagram.
*/
lastStart = (ChessMove) 0;
while (gameNumber > 0) {
yyboardindex = forwardMostMove;
cm = (ChessMove) yylex();
switch (cm) {
case (ChessMove) 0:
Reset(TRUE);
DisplayError("Game not found in file", 0);
return FALSE;
case GNUChessGame:
case XBoardGame:
gameNumber--;
lastStart = cm;
break;
case MoveNumberOne:
if (lastStart == MoveNumberOne || lastStart == (ChessMove) 0) {
gameNumber--;
lastStart = cm;
} else if (lastStart == XBoardGame) {
lastStart = cm;
}
break;
default:
break;
}
}
if (appData.debugMode)
fprintf(debugFP, "Parsed game start '%s' (%d)\n", yytext, (int) cm);
if (lastStart == XBoardGame) {
/* Skip header junk before position diagram and/or move 1 */
for (;;) {
int i, j;
char *p;
Board initial_position;
yyboardindex = forwardMostMove;
cm = (ChessMove) yylex();
if (appData.debugMode)
fprintf(debugFP, "Parsed '%s' (%d)\n", yytext, (int) cm);
switch (cm) {
default:
continue;
case (ChessMove) 0:
case GNUChessGame:
case XBoardGame:
Reset(TRUE);
DisplayMessage("No moves in game");
return FALSE;
case MoveNumberOne:
break;
case PositionDiagram:
p = yytext;
for (i = BOARD_SIZE - 1; i >= 0; i--)
for (j = 0; j < BOARD_SIZE; p++)
switch (*p) {
case '[':
case '-':
case ' ':
case '\t':
case '\n':
case '\r':
break;
default:
initial_position[i][j++] = CharToPiece(*p);
break;
}
while (*p == ' ' || *p == '\t' ||
*p == '\n' || *p == '\r') p++;
if (strncmp(p, "black", strlen("black"))==0)
blackPlaysFirst = TRUE;
else
blackPlaysFirst = FALSE;
startedFromSetupPosition = TRUE;
if (blackPlaysFirst) {
currentMove = forwardMostMove = backwardMostMove = 1;
CopyBoard(boards[0], initial_position);
CopyBoard(boards[1], initial_position);
strcpy(moveList[0], "");
strcpy(parseList[0], "");
timeRemaining[0][1] = whiteTimeRemaining;
timeRemaining[1][1] = blackTimeRemaining;
SendToProgram("a3\n", firstProgramPR);
SendCurrentBoard(firstProgramPR);
} else {
currentMove = forwardMostMove = backwardMostMove = 0;
CopyBoard(boards[0], initial_position);
SendCurrentBoard(firstProgramPR);
}
break;
case Comment:
if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yytext);
if (!matchMode && (pausing || appData.timeDelay != 0)) {
CommentPopUp(yytext);
}
AppendComment(currentMove, yytext);
if (!matchMode && appData.timeDelay != 0) {
DrawPosition(FALSE, boards[currentMove]);
if (appData.timeDelay > 0)
StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
return TRUE;
}
break;
}
break;
}
}
if (!matchMode && appData.timeDelay != 0)
DrawPosition(FALSE, boards[currentMove]);
LoadGameLoop();
return TRUE;
}
void ResurrectChessProgram()
/* Get out of EndOfGame mode. This may require us to restart the
chess program and feed it all the moves made so far. */
{
int i;
/* assert(gameMode == EndOfGame) */
gameMode = lastGameMode = ForceMoves;
ModeHighlight();
if (firstProgramPR != NoProc) return;
InitChessProgram(appData.firstHost, appData.firstChessProgram,
&firstProgramPR, &firstProgramISR, &firstSendTime);
SendToProgram("force\n", firstProgramPR);
if (startedFromSetupPosition) {
if (backwardMostMove % 2 == 1)
SendToProgram("a3\n", firstProgramPR);
SendBoard(firstProgramPR, boards[backwardMostMove]);
}
for (i = backwardMostMove; i < currentMove; i++) {
SendToProgram(moveList[i], firstProgramPR);
}
if (!firstSendTime) {
/* can't tell gnuchess what its clock should read,
so we bow to its notion. */
ResetClocks();
timeRemaining[0][currentMove] = whiteTimeRemaining;
timeRemaining[1][currentMove] = blackTimeRemaining;
}
}
void MachineBlackEvent()
{
if (pausing) PauseEvent();
if (gameMode == PlayFromGameFile) ForceMovesEvent();
if (gameMode == EditPosition) EditPositionDone();
if ((gameMode == EndOfGame) ||
(appData.noChessProgram) || (gameMode == MachinePlaysBlack))
return;
if (WhiteOnMove(gameMode == ForceMoves ? currentMove : forwardMostMove)) {
DisplayError("It is not Black's turn", 0);
return;
}
if (gameMode == TwoMachinesPlay) ForceMovesEvent();
if (gameMode == ForceMoves) forwardMostMove = currentMove;
lastGameMode = gameMode = MachinePlaysBlack;
ModeHighlight();
SendToProgram(appData.blackString, firstProgramPR);
StartClocks();
}
void ForwardInner(target)
int target;
{
if (gameMode == EditPosition)
return;
if (gameMode == PlayFromGameFile && !pausing)
PauseEvent();
if (currentMove >= forwardMostMove) {
if (gameFileFP != NULL)
(void) LoadGameOneMove();
return;
}
if (gameMode == ForceMoves) {
while (currentMove < target) {
SendToProgram(moveList[currentMove++], firstProgramPR);
}
} else {
currentMove = target;
}
if (gameMode == ForceMoves) {
whiteTimeRemaining = timeRemaining[0][currentMove];
blackTimeRemaining = timeRemaining[1][currentMove];
}
DisplayBothClocks();
DisplayMove(currentMove - 1);
DrawPosition(FALSE, boards[currentMove]);
if (commentList[currentMove] == NULL) {
CommentPopDown();
} else {
CommentPopUp(commentList[currentMove]);
}
}
void ForwardEvent()
{
ForwardInner(currentMove + 1);
}
void ToEndEvent()
{
ForwardInner(forwardMostMove);
}
void Reset(redraw)
int redraw;
{
int i;
pausing = FALSE;
flipView = appData.flipView;
startedFromSetupPosition = blackPlaysFirst = FALSE;
firstMove = TRUE;
whiteFlag = blackFlag = FALSE;
maybeThinking = FALSE;
endMessage[0] = NULLCHAR;
ics_white[0] = ics_black[0] = NULLCHAR;
ics_user_moved = FALSE;
ics_getting_history = H_FALSE;
ics_gamenum = -1;
ResetFrontEnd();
ShutdownChessPrograms(NULL);
lastGameMode = gameMode = BeginningOfGame;
ModeHighlight();
InitPosition(redraw);
for (i = 0; i < MAX_MOVES; i++) {
if (commentList[i] != NULL) {
free(commentList[i]);
commentList[i] = NULL;
}
}
ResetClocks();
timeRemaining[0][0] = whiteTimeRemaining;
timeRemaining[1][0] = blackTimeRemaining;
InitChessProgram(appData.firstHost, appData.firstChessProgram,
&firstProgramPR, &firstProgramISR, &firstSendTime);
DisplayTitle("");
DisplayMessage("");
}
void EditPositionEvent()
{
if (gameMode == EditPosition) {
ForceMovesEvent();
return;
}
ForceMovesEvent();
if (gameMode != ForceMoves) return;
lastGameMode = gameMode = EditPosition;
ModeHighlight();
if (currentMove > 0)
CopyBoard(boards[0], boards[currentMove]);
blackPlaysFirst = !WhiteOnMove(currentMove);
ResetClocks();
currentMove = forwardMostMove = backwardMostMove = 0;
}
void EditPositionDone()
{
startedFromSetupPosition = TRUE;
SendToProgram(appData.initString, firstProgramPR);
SendSearchDepth(firstProgramPR);
if (blackPlaysFirst) {
strcpy(moveList[0], "");
strcpy(parseList[0], "");
currentMove = forwardMostMove = backwardMostMove = 1;
CopyBoard(boards[1], boards[0]);
SendToProgram("force\na3\n", firstProgramPR);
SendCurrentBoard(firstProgramPR);
DisplayTitle("");
DisplayMessage("Black to play");
} else {
currentMove = forwardMostMove = backwardMostMove = 0;
SendCurrentBoard(firstProgramPR);
SendToProgram("force\n", firstProgramPR);
DisplayTitle("");
DisplayMessage("White to play");
}
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
lastGameMode = gameMode = ForceMoves;
}
void SetWhiteToPlayEvent()
{
if (gameMode != EditPosition) return;
blackPlaysFirst = FALSE;
DisplayBothClocks(); /* works because currentMove is 0 */
}
void SetBlackToPlayEvent()
{
if (gameMode != EditPosition) return;
blackPlaysFirst = TRUE;
currentMove = 1; /* kludge */
DisplayBothClocks();
currentMove = 0;
}
void EditPositionMenuEvent(selection, x, y)
ChessSquare selection;
int x, y;
{
if (gameMode != EditPosition) return;
switch (selection) {
case ClearBoard:
for (x = 0; x < BOARD_SIZE; x++)
for (y = 0; y < BOARD_SIZE; y++)
boards[0][y][x] = EmptySquare;
DrawPosition(FALSE, boards[0]);
break;
case WhitePlay:
SetWhiteToPlayEvent();
break;
case BlackPlay:
SetBlackToPlayEvent();
break;
default:
boards[0][y][x] = selection;
DrawPosition(FALSE, boards[0]);
break;
}
}
void MachineWhiteEvent()
{
if (pausing) PauseEvent();
if (gameMode == PlayFromGameFile) ForceMovesEvent();
if (gameMode == EditPosition) EditPositionDone();
if ((gameMode == EndOfGame) ||
(appData.noChessProgram) || (gameMode == MachinePlaysWhite))
return;
if (!WhiteOnMove(gameMode == ForceMoves ? currentMove : forwardMostMove)) {
DisplayError("It is not White's turn", 0);
return;
}
if (gameMode == TwoMachinesPlay) ForceMovesEvent();
if (gameMode == ForceMoves) forwardMostMove = currentMove;
lastGameMode = gameMode = MachinePlaysWhite;
ModeHighlight();
SendToProgram(appData.whiteString, firstProgramPR);
StartClocks();
}
void BackwardInner(target)
int target;
{
if ((currentMove <= backwardMostMove) || (gameMode == EditPosition))
return;
if (gameMode == EndOfGame)
ResurrectChessProgram();
if (gameMode == PlayFromGameFile && !pausing)
PauseEvent();
if (gameMode == ForceMoves) {
Attention(firstProgramPR);
while (currentMove > target) {
SendToProgram("undo\n", firstProgramPR);
currentMove--;
}
} else {
currentMove = target;
}
if (gameMode == ForceMoves) {
whiteTimeRemaining = timeRemaining[0][currentMove];
blackTimeRemaining = timeRemaining[1][currentMove];
}
DisplayBothClocks();
DisplayMove(currentMove - 1);
DrawPosition(FALSE, boards[currentMove]);
if (commentList[currentMove] == NULL) {
CommentPopDown();
} else {
CommentPopUp(commentList[currentMove]);
}
}
void BackwardEvent()
{
BackwardInner(currentMove - 1);
}
void ToStartEvent()
{
BackwardInner(backwardMostMove);
}
void FlipViewEvent()
{
flipView = !flipView;
DrawPosition(FALSE, boards[currentMove]);
}
char *DefaultFileName(ext)
char *ext;
{
static char def[MSG_SIZ];
if (appData.icsActive && ics_white[0] != NULLCHAR) {
sprintf(def, "%s-%s.%s", ics_white, ics_black, ext);
} else {
def[0] = NULLCHAR;
}
return def;
}
int SaveGameToFile(filename)
char *filename;
{
FILE *f;
char buf[MSG_SIZ];
f = fopen(filename, "a");
if (f == NULL) {
sprintf(buf, "Can't open %s", filename);
DisplayError(buf, errno);
return FALSE;
} else {
SaveGame(f, 0);
return TRUE;
}
}
int SaveGame(f, dummy)
FILE *f;
int dummy;
{
int i;
time_t tm;
tm = time((time_t *) NULL);
fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
PrintOpponents(f);
fprintf(f, "\talgebraic\n");
if (backwardMostMove > 0 || startedFromSetupPosition) {
fprintf(f, "[--------------\n");
PrintPosition(f, backwardMostMove);
fprintf(f, "--------------]\n");
}
i = backwardMostMove;
while (i < forwardMostMove) {
if (commentList[i] != NULL)
fprintf(f, "%s\n", commentList[i]);
if ((i % 2) == 1) {
fprintf(f, "%d. ... %s\n", i/2 + 1, parseList[i]);
i++;
} else {
fprintf(f, "%d. %s ", i/2 + 1, parseList[i]);
i++;
if (commentList[i] != NULL) {
fprintf(f, "\n");
continue;
}
if (i >= forwardMostMove) {
fprintf(f, "\n");
break;
}
fprintf(f, "%s\n", parseList[i]);
i++;
}
}
if (commentList[i] != NULL)
fprintf(f, "%s\n", commentList[i]);
if (endMessage[0] != NULLCHAR)
fprintf(f, "%s\n", endMessage);
fclose(f);
return TRUE;
}
#ifdef notdef
/* currently unused */
void SwitchEvent()
{
if (appData.noChessProgram) return;
if (pausing) PauseEvent();
switch (gameMode) {
default:
return;
case MachinePlaysWhite:
if (WhiteOnMove(forwardMostMove)) {
DisplayError("Wait until your turn", 0);
return;
}
lastGameMode = gameMode = MachinePlaysBlack;
ModeHighlight();
break;
case BeginningOfGame:
case MachinePlaysBlack:
if (!WhiteOnMove(forwardMostMove)) {
DisplayError("Wait until your turn", 0);
return;
}
if (forwardMostMove == 0) {
MachineWhiteEvent();
return;
}
lastGameMode = gameMode = MachinePlaysWhite;
ModeHighlight();
break;
}
Attention(firstProgramPR);
SendToProgram("switch\n", firstProgramPR);
}
#endif /*notdef*/
void ForceMovesEvent()
{
int i;
char str[MSG_SIZ];
if (pausing) PauseEvent();
switch (gameMode) {
case MachinePlaysWhite:
if (WhiteOnMove(forwardMostMove)) {
DisplayError("Wait until your turn", 0);
return;
}
Attention(firstProgramPR);
SendToProgram("force\n", firstProgramPR);
break;
case MachinePlaysBlack:
if (!WhiteOnMove(forwardMostMove)) {
DisplayError("Wait until your turn", 0);
return;
}
Attention(firstProgramPR);
SendToProgram("force\n", firstProgramPR);
break;
case BeginningOfGame:
SendToProgram("force\n", firstProgramPR);
break;
case PlayFromGameFile:
(void) StopLoadGameTimer();
if (gameFileFP != NULL) {
gameFileFP = NULL;
}
break;
case EndOfGame:
ResurrectChessProgram();
return;
case EditPosition:
EditPositionDone();
break;
case TwoMachinesPlay:
ShutdownChessPrograms(NULL);
ResurrectChessProgram();
return;
case IcsPlayingBlack:
case IcsPlayingWhite:
DisplayError("Aren't you playing a game? If not, use Reset and try again.", 0);
return;
case IcsObserving:
SendToICS("observe\n");
sprintf(str, "Aren't you observing game %d? Attempting to stop observing it.",
ics_gamenum);
DisplayError(str, 0);
break;
case IcsIdle:
break;
default:
return;
}
if (gameMode == MachinePlaysWhite ||
gameMode == MachinePlaysBlack ||
gameMode == TwoMachinesPlay ||
gameMode == PlayFromGameFile) {
i = forwardMostMove;
while (i > currentMove) {
SendToProgram("undo\n", firstProgramPR);
i--;
}
whiteTimeRemaining = timeRemaining[0][currentMove];
blackTimeRemaining = timeRemaining[1][currentMove];
if (whiteFlag || blackFlag) {
whiteFlag = blackFlag = 0;
}
DisplayTitle("");
}
lastGameMode = gameMode = ForceMoves;
ModeHighlight();
StopClocks();
}
void IcsClientEvent()
{
if (!appData.icsActive) return;
switch (gameMode) {
case IcsPlayingWhite:
case IcsPlayingBlack:
case IcsObserving:
case IcsIdle:
case BeginningOfGame:
return;
case ForceMoves:
break;
case EditPosition:
EditPositionDone();
break;
default:
ForceMovesEvent();
break;
}
gameMode = IcsIdle;
ModeHighlight();
return;
}
void HintEvent()
{
if (appData.noChessProgram) return;
switch (gameMode) {
case MachinePlaysWhite:
if (WhiteOnMove(forwardMostMove)) {
DisplayError("Wait until your turn", 0);
return;
}
break;
case BeginningOfGame:
case MachinePlaysBlack:
if (!WhiteOnMove(forwardMostMove)) {
DisplayError("Wait until your turn", 0);
return;
}
break;
default:
DisplayError("No hint available", 0);
return;
}
Attention(firstProgramPR);
SendToProgram("hint\n", firstProgramPR);
}
void PrintPosition(fp, move)
FILE *fp;
int move;
{
int i, j;
for (i = BOARD_SIZE - 1; i >= 0; i--) {
for (j = 0; j < BOARD_SIZE; j++) {
fprintf(fp, "%c", PieceToChar(boards[move][i][j]));
fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
}
}
if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
fprintf(fp, "white to play\n");
else
fprintf(fp, "black to play\n");
}
void PrintOpponents(fp)
FILE *fp;
{
switch (lastGameMode) {
case MachinePlaysWhite:
fprintf(fp, "\t%s@%s vs. %s@%s\n", appData.firstChessProgram,
appData.firstHost, UserName(), HostName());
break;
case MachinePlaysBlack:
fprintf(fp, "\t%s@%s vs. %s@%s\n", UserName(),
HostName(), appData.firstChessProgram,
appData.firstHost);
break;
case TwoMachinesPlay:
fprintf(fp, "\t%s@%s vs. %s@%s\n", appData.secondChessProgram,
appData.secondHost, appData.firstChessProgram,
appData.firstHost);
break;
default:
if (appData.icsActive && ics_white[0] != NULLCHAR) {
fprintf(fp, "\t%s vs. %s\n",
ics_white, ics_black);
} else {
fprintf(fp, "\n");
}
break;
}
}
int SavePositionToFile(filename)
char *filename;
{
FILE *f;
char buf[MSG_SIZ];
f = fopen(filename, "a");
if (f == NULL) {
sprintf(buf, "Can't open %s", filename);
DisplayError(buf, errno);
return FALSE;
} else {
SavePosition(f, 0);
return TRUE;
}
}
int SavePosition(f, dummy)
FILE *f;
int dummy;
{
time_t tm;
tm = time((time_t *) NULL);
fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
PrintOpponents(f);
fprintf(f, "[--------------\n");
PrintPosition(f, currentMove);
fprintf(f, "--------------]\n");
fclose(f);
return TRUE;
}
void TwoMachinesEvent()
{
int i;
if (pausing) PauseEvent();
if (gameMode == PlayFromGameFile) ForceMovesEvent();
if (appData.noChessProgram) return;
switch (gameMode) {
case EndOfGame:
case TwoMachinesPlay:
return;
case MachinePlaysWhite:
case MachinePlaysBlack:
case BeginningOfGame:
ForceMovesEvent();
if (gameMode != ForceMoves) return;
break;
case EditPosition:
EditPositionDone();
break;
case ForceMoves:
default:
break;
}
forwardMostMove = currentMove;
InitChessProgram(appData.secondHost, appData.secondChessProgram,
&secondProgramPR, &secondProgramISR, &secondSendTime);
if (startedFromSetupPosition) {
if (blackPlaysFirst) {
SendToProgram("force\na3\n", secondProgramPR);
SendBoard(secondProgramPR, boards[backwardMostMove]);
} else {
SendBoard(secondProgramPR, boards[backwardMostMove]);
SendToProgram("force\n", secondProgramPR);
}
} else {
SendToProgram("force\n", secondProgramPR);
}
for (i = backwardMostMove; i < forwardMostMove; i++) {
SendToProgram(moveList[i], secondProgramPR);
}
lastGameMode = gameMode = TwoMachinesPlay;
ModeHighlight();
firstMove = TRUE;
if (WhiteOnMove(forwardMostMove))
SendToProgram(appData.whiteString, secondProgramPR);
else
SendToProgram(appData.blackString, firstProgramPR);
if (!firstSendTime || !secondSendTime) {
ResetClocks();
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
}
StartClocks();
}
void PauseEvent()
{
if (pausing) {
pausing = FALSE;
ModeHighlight();
if (gameMode == MachinePlaysWhite ||
gameMode == MachinePlaysBlack) {
StartClocks();
} else {
DisplayBothClocks();
}
if (gameMode == PlayFromGameFile &&
!LoadGameTimerRunning() &&
appData.timeDelay >= 0) {
StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
}
} else {
switch (gameMode) {
case EndOfGame:
case EditPosition:
default:
return;
case IcsObserving:
case IcsPlayingWhite:
case IcsPlayingBlack:
pausing = TRUE;
ModeHighlight();
return;
case PlayFromGameFile:
(void) StopLoadGameTimer();
pausing = TRUE;
ModeHighlight();
break;
case BeginningOfGame:
if (appData.icsActive) return;
/* else fall through */
case MachinePlaysWhite:
case MachinePlaysBlack:
case TwoMachinesPlay:
if (forwardMostMove == 0)
return; /* don't pause if no one has moved */
if ((gameMode == MachinePlaysWhite &&
!WhiteOnMove(forwardMostMove)) ||
(gameMode == MachinePlaysBlack &&
WhiteOnMove(forwardMostMove))) {
StopClocks();
}
pausing = TRUE;
ModeHighlight();
break;
}
}
}
void EditCommentEvent()
{
EditCommentPopUp(currentMove, commentList[currentMove]);
}
void ReplaceComment(index, text)
int index;
char *text;
{
int len;
len = strlen(text);
if (commentList[index] != NULL)
free(commentList[index]);
if (len == 0) {
commentList[index] = NULL;
return;
}
commentList[index] = (char *) malloc(len + 3);
if (text[0] != '[' ||
(text[len-1] != ']' && text[len-2] != ']')) {
strcpy(commentList[index], "[");
strcat(commentList[index], text);
strcat(commentList[index], "]");
} else {
strcpy(commentList[index], text);
}
}
void AppendComment(index, text)
int index;
char *text;
{
int oldlen, len;
char *old;
len = strlen(text);
if (len == 0) return;
if (commentList[index] != NULL) {
old = commentList[index];
oldlen = strlen(old);
commentList[index] = (char *) malloc(oldlen + len + 4);
strcpy(commentList[index], old);
free(old);
if (commentList[index][oldlen - 1] != '\n')
strcat(commentList[index], "\n");
} else {
commentList[index] = (char *) malloc(len + 3);
commentList[index][0] = NULLCHAR;
}
if (text[0] != '[' ||
(text[len-1] != ']' && text[len-2] != ']')) {
strcat(commentList[index], "[");
strcat(commentList[index], text);
strcat(commentList[index], "]");
} else {
strcat(commentList[index], text);
}
}
void SendToProgram(message, pr)
char *message;
ProcRef pr;
{
int count, outCount, error;
char *which;
char buf[MSG_SIZ];
if (pr == NULL) return;
lastMsgPR = pr;
which = (pr == firstProgramPR) ? "first" : "second";
if (appData.debugMode)
fprintf(debugFP, "Sending to %s: %s", which, message);
count = strlen(message);
outCount = OutputToProcess(pr, message, count, &error);
if (outCount < count) {
sprintf(buf, "Error writing to %s chess program", which);
DisplayFatalError(buf, error);
ExitEvent(1);
}
}
void ReceiveFromProgram(isr, message, count, error)
InputSourceRef isr;
char *message;
int count;
int error;
{
char *end_str, *name, *which;
if (count <= 0) {
if (isr == firstProgramISR) {
which = "first";
name = appData.firstChessProgram;
} else if (isr == secondProgramISR) {
which = "second";
name = appData.secondChessProgram;
} else {
return;
}
if (count == 0) {
sprintf(message, "Error: %s chess program (%s) exited unexpectedly",
which, name);
DisplayFatalError(message, 0);
} else {
sprintf(message,
"Error reading from %s chess program (%s)",
which, name);
DisplayFatalError(message, error);
}
ShutdownChessPrograms(message);
return; /* don't actually exit */
}
if ((end_str = strchr(message, '\r')) != NULL)
*end_str = NULLCHAR;
if ((end_str = strchr(message, '\n')) != NULL)
*end_str = NULLCHAR;
if (appData.debugMode)
fprintf(debugFP, "Received from %s: %s\n",
isr == firstProgramISR ? "first" : "second", message);
HandleMachineMove(message, isr);
}
void SendSearchDepth(pr)
ProcRef pr;
{
char message[MSG_SIZ];
if (appData.searchDepth <= 0) return;
sprintf(message, "depth\n%d\nhelp\n", appData.searchDepth);
/* note kludge: "help" command forces gnuchessx to print
out something that ends with a newline. */
SendToProgram(message, pr);
}
void SendTimeRemaining(pr)
ProcRef pr;
{
char message[MSG_SIZ];
long time;
if (WhiteOnMove(forwardMostMove))
time = whiteTimeRemaining;
else
time = blackTimeRemaining;
if (time <= 0) time = 1000;
sprintf(message, "time %d\n", time/10);
SendToProgram(message, pr);
}
void DisplayMove(moveNumber)
int moveNumber;
{
char message[MSG_SIZ];
if (moveNumber < 0) {
if (moveNumber == forwardMostMove - 1)
DisplayMessage(endMessage);
else
DisplayMessage("");
} else {
sprintf(message, "%d. %s%s %s", moveNumber / 2 + 1,
WhiteOnMove(moveNumber) ? "" : "... ",
parseList[moveNumber],
moveNumber == forwardMostMove - 1 ? endMessage : "");
DisplayMessage(message);
}
}
/*
* This routine sends a ^C interrupt to gnuchess to awaken it
* if it might be busy thinking on our time. This normally isn't needed,
* but is useful on systems where the FIONREAD ioctl doesn't work (such
* as ESIX), since on those systems the gnuchess feature that lets you
* interrupt its thinking just by typing a command does not work.
*
* In the future, similar code could be used to stop gnuchess and make
* it move immediately when it is thinking about its own move; this could
* be useful if we want to make Backward or ForceMoves work while gnuchess
* is thinking. --t.mann
*/
void Attention(pr)
ProcRef pr;
{
#if defined(ATTENTION) || defined(ESIX)
if (appData.noChessProgram || (pr == NoProc)) return;
switch (gameMode) {
case MachinePlaysWhite:
case MachinePlaysBlack:
case TwoMachinesPlay:
if (forwardMostMove > backwardMostMove + 1 && maybeThinking) {
if (appData.debugMode)
fprintf(debugFP, "Interrupting %s\n",
pr == firstProgramPR ? "first" : "second");
InterruptChildProcess(pr);
}
break;
}
#endif /*ATTENTION*/
}
void CheckFlags()
{
if (whiteTimeRemaining <= 0) {
if (!whiteFlag) {
whiteFlag = TRUE;
if (appData.icsActive) {
if (appData.autoCallFlag &&
gameMode == IcsPlayingBlack && !blackFlag)
SendToICS("flag\n");
} else {
if (blackFlag)
DisplayTitle("Both flags have fallen");
else
DisplayTitle("White's flag has fallen");
}
}
}
if (blackTimeRemaining <= 0) {
if (!blackFlag) {
blackFlag = TRUE;
if (appData.icsActive) {
if (appData.autoCallFlag &&
gameMode == IcsPlayingWhite && !whiteFlag)
SendToICS("flag\n");
} else {
if (whiteFlag)
DisplayTitle("Both flags have fallen");
else
DisplayTitle("Black's flag has fallen");
}
}
}
}
void CheckTimeControl()
{
if (!appData.clockMode || appData.icsActive ||
gameMode == PlayFromGameFile || forwardMostMove == 0) return;
/*
* add time to clocks when time control is achieved
*/
if ((forwardMostMove % (appData.movesPerSession * 2)) == 0) {
whiteTimeRemaining += timeControl;
blackTimeRemaining += timeControl;
}
}
void DisplayBothClocks()
{
DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
}
/* Timekeeping seems to be a portability nightmare. I think everyone
has ftime(), but I'm really not sure, so I'm including some ifdefs
to use other calls if you don't. Clocks will be less accurate if
you have neither ftime nor gettimeofday.
*/
/* A point in time */
typedef struct {
long sec; /* Assuming this is >= 32 bits */
int ms; /* Assuming this is >= 16 bits */
} TimeMark;
/* Get the current time as a TimeMark */
void GetTimeMark(tm)
TimeMark *tm;
{
#ifdef HAS_GETTIMEOFDAY
#if !defined(ESIX)
#include <sys/time.h>
#endif
struct timeval timeVal;
struct timezone timeZone;
gettimeofday(&timeVal, &timeZone);
tm->sec = (long) timeVal.tv_sec;
tm->ms = (int) (timeVal.tv_usec / 1000L);
#else /*!HAS_GETTIMEOFDAY*/
#ifdef HAS_FTIME
#include <sys/timeb.h>
struct timeb timeB;
ftime(&timeB);
tm->sec = (long) timeB.time;
tm->ms = (int) timeB.millitm;
#else /*!HAS_FTIME && !HAS_GETTIMEOFDAY*/
tm->sec = (long) time(NULL);
tm->ms = 0;
#endif
#endif
}
/* Return the difference in milliseconds between two
time marks. We assume the difference will fit in a long!
*/
long SubtractTimeMarks(tm2, tm1)
TimeMark *tm2, *tm1;
{
return 1000L*(tm2->sec - tm1->sec) +
(long) (tm2->ms - tm1->ms);
}
/*
* Code to manage the game clocks.
*
* In tournament play, black starts the clock and then white makes a move.
* We give the human user a slight advantage if he is playing white---the
* clocks don't run until he makes his first move, so it takes zero time.
* Also, we doesn't account for network lag, so we could get
* out of sync with GNU Chess's clock -- but then, referees are always right.
*/
static TimeMark tickStartTM;
static long intendedTickLength;
long NextTickLength(timeRemaining)
long timeRemaining;
{
long nominalTickLength, nextTickLength;
if (timeRemaining > 0L && timeRemaining <= 1000L)
nominalTickLength = 100L;
else
nominalTickLength = 1000L;
nextTickLength = timeRemaining % nominalTickLength;
if (nextTickLength <= 0) nextTickLength += nominalTickLength;
return nextTickLength;
}
/* Stop clocks and reset to a fresh time control */
void ResetClocks()
{
(void) StopClockTimer();
whiteTimeRemaining = timeControl;
blackTimeRemaining = timeControl;
if (whiteFlag || blackFlag) {
DisplayTitle("");
whiteFlag = blackFlag = FALSE;
}
DisplayBothClocks();
}
#define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
/* Decrement running clock by amount of time that has passed */
void DecrementClocks()
{
long timeRemaining;
long lastTickLength, fudge;
TimeMark now;
if (!appData.clockMode) return;
GetTimeMark(&now);
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
/* Fudge if we woke up a little too soon */
fudge = intendedTickLength - lastTickLength;
if (fudge < 0 || fudge > FUDGE) fudge = 0;
/*!!*/if (appData.debugMode && fudge) fprintf(debugFP, "fudging %d ", fudge);
if (WhiteOnMove(forwardMostMove)) {
timeRemaining = whiteTimeRemaining -= lastTickLength;
DisplayWhiteClock(whiteTimeRemaining - fudge,
WhiteOnMove(currentMove));
} else {
timeRemaining = blackTimeRemaining -= lastTickLength;
DisplayBlackClock(blackTimeRemaining - fudge,
!WhiteOnMove(currentMove));
}
CheckFlags();
tickStartTM = now;
intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
StartClockTimer(intendedTickLength);
}
/* A player has just moved, so stop the previously running
clock and (if in clock mode) start the other one.
We redisplay both clocks in case we're in ICS mode, because
ICS gives us an update to both clocks after every move.
*/
void SwitchClocks()
{
long lastTickLength;
TimeMark now;
GetTimeMark(&now);
if (StopClockTimer() && appData.clockMode) {
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
if (WhiteOnMove(forwardMostMove)) {
whiteTimeRemaining -= lastTickLength;
} else {
blackTimeRemaining -= lastTickLength;
}
CheckFlags();
}
CheckTimeControl();
DisplayBothClocks();
if (!appData.clockMode) return;
switch (gameMode) {
case MachinePlaysBlack:
case MachinePlaysWhite:
case BeginningOfGame:
if (pausing) return;
break;
case ForceMoves:
case PlayFromGameFile:
return;
default:
break;
}
tickStartTM = now;
intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
whiteTimeRemaining : blackTimeRemaining);
StartClockTimer(intendedTickLength);
}
/* Stop both clocks */
void StopClocks()
{
long lastTickLength;
TimeMark now;
if (!StopClockTimer()) return;
if (!appData.clockMode) return;
GetTimeMark(&now);
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
if (WhiteOnMove(forwardMostMove)) {
whiteTimeRemaining -= lastTickLength;
DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
} else {
blackTimeRemaining -= lastTickLength;
DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
}
CheckFlags();
}
/* Start clock of player on move. Time may have been reset, so
if clock is already running, stop and restart it. */
void StartClocks()
{
(void) StopClockTimer(); /* in case it was running already */
DisplayBothClocks();
if (!appData.clockMode) return;
GetTimeMark(&tickStartTM);
intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
whiteTimeRemaining : blackTimeRemaining);
StartClockTimer(intendedTickLength);
}
char *TimeString(ms)
long ms;
{
long second, minute, hour, day;
char *sign = "";
static char buf[32];
if (ms > 0 && ms <= 900) {
/* convert milliseconds to tenths, rounding up */
sprintf(buf, " 0.%1d ", (ms+99L)/100L);
return buf;
}
/* convert milliseconds to seconds, rounding up */
/* use floating point to avoid strangeness of integer division
with negative dividends on many machines */
second = (long) floor(((double) (ms + 999L)) / 1000.0);
if (second < 0) {
sign = "-";
second = -second;
}
day = second / (60 * 60 * 24);
second = second % (60 * 60 * 24);
hour = second / (60 * 60);
second = second % (60 * 60);
minute = second / 60;
second = second % 60;
if (day > 0)
sprintf(buf, " %s%d:%02d:%02d:%02d ", sign, day, hour, minute, second);
else if (hour > 0)
sprintf(buf, " %s%d:%02d:%02d ", sign, hour, minute, second);
else
sprintf(buf, " %s%2d:%02d ", sign, minute, second);
return buf;
}
/*
* This is necessary because some C libraries aren't ANSI C compliant yet.
*/
char *StrStr(string, match)
char *string, *match;
{
int i, length;
length = strlen(match);
for (i = strlen(string) - length; i >= 0; i--, string++)
if (!strncmp(match, string, (size_t) length))
return string;
return NULL;
}
int StrCaseCmp(s1, s2)
char *s1, *s2;
{
char c1, c2;
for (;;) {
c1 = ToLower(*s1++);
c2 = ToLower(*s2++);
if (c1 > c2) return 1;
if (c1 < c2) return -1;
if (c1 == NULLCHAR) return 0;
}
}
int ToLower(c)
int c;
{
return isupper(c) ? tolower(c) : c;
}
int ToUpper(c)
int c;
{
return islower(c) ? toupper(c) : c;
}