/*
 * XBoard -- an Xt user interface for GNU Chess
 *
 * Dan Sears
 * Chris Sears
 *
 * XBoard borrows its colors, icon and piece bitmaps from XChess
 * which was written and is copyrighted by Wayne Christopher.
 *
 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
 *
 * 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.
 *
 * Revision 1.0 90/10/31
 *		Initial release.
 *
 * Revision 1.1 91/01/26
 *		Major bug fix release.
 */

#include <stdio.h>
#include <signal.h>
#include <pwd.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/List.h>
#include <X11/Xaw/Label.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>
#include "xboard.h"

#include "bitmaps/solid_pawn.bitmap"
#include "bitmaps/solid_rook.bitmap"
#include "bitmaps/solid_knight.bitmap"
#include "bitmaps/solid_bishop.bitmap"
#include "bitmaps/solid_queen.bitmap"
#include "bitmaps/solid_king.bitmap"

#include "bitmaps/outline_pawn.bitmap"
#include "bitmaps/outline_rook.bitmap"
#include "bitmaps/outline_knight.bitmap"
#include "bitmaps/outline_bishop.bitmap"
#include "bitmaps/outline_queen.bitmap"
#include "bitmaps/outline_king.bitmap"

#include "bitmaps/pawn_small.bitmap"
#include "bitmaps/rook_small.bitmap"
#include "bitmaps/knight_small.bitmap"
#include "bitmaps/bishop_small.bitmap"
#include "bitmaps/queen_small.bitmap"
#include "bitmaps/king_small.bitmap"

#include "bitmaps/pawn_small_outline.bitmap"
#include "bitmaps/rook_small_outline.bitmap"
#include "bitmaps/knight_small_outline.bitmap"
#include "bitmaps/bishop_small_outline.bitmap"
#include "bitmaps/queen_small_outline.bitmap"
#include "bitmaps/king_small_outline.bitmap"

#include "bitmaps/icon.bitmap"

#ifdef __STDC__
void main(int argc, char **argv);
void CreateGCs(void);
void CreatePieces(void);
void ReadBitmap(String name, Pixmap *pm, char big_bits[], char small_bits[]);
void CreateGrid(void);
int EventToSquare(int x);
int CharToPiece(int c);
void DrawSquare(int row, int column, int piece);
void DrawPosition(Widget w, XExposeEvent *event);
void InitPosition(void);
void CopyBoard(Board to, Board from);
void SendCurrentBoard(FILE *fp);
void HandleUserMove(Widget w, XEvent *event);
void HandleMachineMove(char *message);
void ReadGameFile(void);
void MakeMove(int *move_type, int from_x, int from_y, int to_x, int to_y);
void InitChessProgram(char *host_name, char *program_name, int *pid,
	FILE **to, FILE **from, XtIntervalId *xid);
void ShutdownChessPrograms(char *message);
void SelectCommand(Widget w, XtPointer client_data, XtPointer call_data);
void QuitProc(void);
int PlayFromGameFileProc(void);
void MachinePlaysBlackProc(void);
void ForwardProc(void);
void ResetProc(void);
int SetupPositionFromFileProc(void);
void MachinePlaysWhiteProc(void);
void BackwardProc(void);
void FlipProc(void);
void SaveGameProc(void);
void SwitchProc(void);
void ForceProc(void);
void HintProc(void);
void SavePositionProc(void);
void TwoMachinesPlayProc(void);
void PauseProc(void);
void Iconify(void);
void SendToProgram(char *message, FILE *fp);
void ReceiveFromProgram(FILE *fp);
void DisplayMessage(char *message);
void DisplayClocks(int clock_mode);
void DisplayTimerLabel(Widget w, char *color, time_t timer);
char *TimeString(time_t tm);
void CatchPipeSignal(void);
void Usage(void);
char *StrStr(char *string, char *match);
#else
void main();
void CreateGCs();
void CreatePieces();
void ReadBitmap();
void CreateGrid();
int EventToSquare();
int CharToPiece();
void DrawSquare();
void DrawPosition();
void InitPosition();
void CopyBoard();
void SendCurrentBoard();
void HandleUserMove();
void HandleMachineMove();
void ReadGameFile();
void MakeMove();
void InitChessProgram();
void ShutdownChessPrograms();
void SelectCommand();
void QuitProc();
int PlayFromGameFileProc();
void MachinePlaysBlackProc();
void ForwardProc();
void ResetProc();
int SetupPositionFromFileProc();
void MachinePlaysWhiteProc();
void BackwardProc();
void FlipProc();
void SaveGameProc();
void SwitchProc();
void ForceProc();
void HintProc();
void SavePositionProc();
void TwoMachinesPlayProc();
void PauseProc();
void Iconify();
void SendToProgram();
void ReceiveFromProgram();
void DisplayMessage();
void DisplayClocks();
void DisplayTimerLabel();
char *TimeString();
void CatchPipeSignal();
void Usage();
char *StrStr();
#endif

int xtVersion = XtSpecificationRelease;	/* XBoard depends on Xt R4 or higher */
int xScreen;
Display *xDisplay;
Window xBoardWindow;
GC lightSquareGC, darkSquareGC, lineGC, wdPieceGC, wlPieceGC,
	bdPieceGC, blPieceGC, wbPieceGC, bwPieceGC;
Pixmap solidPawnBitmap, solidRookBitmap, solidKnightBitmap, solidBishopBitmap,
	solidQueenBitmap, solidKingBitmap, outlinePawnBitmap, outlineRookBitmap,
	outlineKnightBitmap, outlineBishopBitmap, outlineQueenBitmap,
	outlineKingBitmap, iconPixmap;
Widget shellWidget, formWidget, boardWidget, commandsWidget, messageWidget,
	whiteTimerWidget, blackTimerWidget, widgetList[5];
XSegment gridSegments[(BOARD_SIZE + 1) * 2];
XtIntervalId firstProgramXID = NULL, secondProgramXID = NULL,
	readGameXID = NULL, timerXID = NULL;
XFontStruct *labelFont;

FILE *fromFirstProgFP, *toFirstProgFP, *fromSecondProgFP,
	*toSecondProgFP, *gameFileFP;
int currentMove = 0, firstMove = True, forwardMostMove = 0, flipView = False,
	forwardForce = False, gameMode = BeginningOfGame, matchMode = MatchFalse,
	firstProgramPID = 0, secondProgramPID = 0, squareSize = BIG_SQUARE_SIZE,
	fromX = -1, fromY = -1, twoProgramState = False,
	lastGameMode = BeginningOfGame;
char moveList[MAX_MOVES][8];
time_t whiteTimeRemaining, blackTimeRemaining;

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 }
};

String buttonStrings[] = {
	"Quit", "Play From File", "Machine Black", "Forward",
	"Reset", "Setup From File", "Machine White", "Backward",
	"Flip View", "Save Game", "Switch Sides", "Force Moves",
	"Hint", "Save Position", "Two Machines", "Pause"
};

Arg shellArgs[] = {
	{ XtNwidth, 0 },
	{ XtNheight, 0 },
	{ XtNminWidth, 0 },
	{ XtNminHeight, 0 },
	{ XtNmaxWidth, 0 },
	{ XtNmaxHeight, 0 }
};

Arg boardArgs[] = {
	{ XtNborderWidth, 0 },
	{ XtNwidth, LINE_GAP + BOARD_SIZE * (BIG_SQUARE_SIZE + LINE_GAP) },
	{ XtNheight, LINE_GAP + BOARD_SIZE * (BIG_SQUARE_SIZE + LINE_GAP) }
};

Arg commandsArgs[] = {
	{ XtNborderWidth, 0 },
	{ XtNdefaultColumns, 4 },
	{ XtNforceColumns, True },
	{ XtNlist, (int) buttonStrings },
	{ XtNnumberStrings, XtNumber(buttonStrings) }
};

Arg messageArgs[] = {
	{ XtNborderWidth, 0 },
	{ XtNwidth, 500 },
	{ XtNjustify, XtJustifyLeft }
};

Arg timerArgs[] = {
	{ XtNborderWidth, 0 },
	{ XtNjustify, XtJustifyLeft }
};

typedef struct {
	Pixel whitePieceColor;
	Pixel blackPieceColor;
	Pixel lightSquareColor;
	Pixel darkSquareColor;
	int movesPerSession;
	String initString;
	String firstChessProgram;
	String secondChessProgram;
	String firstHost;
	String secondHost;
	String solidPawnBitmap;
	String solidRookBitmap;
	String solidBishopBitmap;
	String solidKnightBitmap;
	String solidQueenBitmap;
	String solidKingBitmap;
	String outlinePawnBitmap;
	String outlineRookBitmap;
	String outlineBishopBitmap;
	String outlineKnightBitmap;
	String outlineQueenBitmap;
	String outlineKingBitmap;
	String remoteShell;
	float timeDelay;
	int timeControl;
	String saveGameFile;
	String readGameFile;
	String savePositionFile;
	String readPositionFile;
	String matchMode;
	Boolean monoMode;
	Boolean debugMode;
	Boolean clockMode;
	Boolean bigSizeMode;
	int searchTime;
} AppData, *AppDataPtr;

AppData appData;

XtResource clientResources[] = {
	{
		"whitePieceColor", "whitePieceColor", XtRPixel, sizeof(Pixel),
		XtOffset(AppDataPtr, whitePieceColor), XtRString, WHITE_PIECE_COLOR
	}, {
		"blackPieceColor", "blackPieceColor", XtRPixel, sizeof(Pixel),
		XtOffset(AppDataPtr, blackPieceColor), XtRString, BLACK_PIECE_COLOR
	}, {
		"lightSquareColor", "lightSquareColor", XtRPixel, sizeof(Pixel),
		XtOffset(AppDataPtr, lightSquareColor), XtRString, LIGHT_SQUARE_COLOR
	}, {
		"darkSquareColor", "darkSquareColor", XtRPixel, sizeof(Pixel),
		XtOffset(AppDataPtr, darkSquareColor), XtRString, DARK_SQUARE_COLOR
	}, {
		"movesPerSession", "movesPerSession", XtRInt, sizeof(int),
		XtOffset(AppDataPtr, movesPerSession), XtRImmediate,
		(XtPointer) MOVES_PER_SESSION
	}, {
		"initString", "initString", XtRString, sizeof(String),
		XtOffset(AppDataPtr, initString), XtRString, INIT_STRING
	}, {
		"firstChessProgram", "firstChessProgram", XtRString, sizeof(String),
		XtOffset(AppDataPtr, firstChessProgram), XtRString, FIRST_CHESS_PROGRAM
	}, {
		"secondChessProgram", "secondChessProgram", XtRString, sizeof(String),
		XtOffset(AppDataPtr, secondChessProgram), XtRString,
		SECOND_CHESS_PROGRAM
	}, {
		"firstHost", "firstHost", XtRString, sizeof(String),
		XtOffset(AppDataPtr, firstHost), XtRString, FIRST_HOST
	}, {
		"secondHost", "secondHost", XtRString, sizeof(String),
		XtOffset(AppDataPtr, secondHost), XtRString, SECOND_HOST
	}, {
		"solidPawnBitmap", "solidPawnBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, solidPawnBitmap), XtRString, SOLID_PAWN_BITMAP
	}, {
		"solidRookBitmap", "solidRookBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, solidRookBitmap), XtRString, SOLID_ROOK_BITMAP
	}, {
		"solidKnightBitmap", "solidKnightBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, solidKnightBitmap), XtRString, SOLID_KNIGHT_BITMAP
	}, {
		"solidBishopBitmap", "solidBishopBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, solidBishopBitmap), XtRString, SOLID_BISHOP_BITMAP
	}, {
		"solidQueenBitmap", "solidQueenBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, solidQueenBitmap), XtRString, SOLID_QUEEN_BITMAP
	}, {
		"solidKingBitmap", "solidKingBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, solidKingBitmap), XtRString, SOLID_KING_BITMAP
	}, {
		"outlinePawnBitmap", "outlinePawnBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, outlinePawnBitmap), XtRString, OUTLINE_PAWN_BITMAP
	}, {
		"outlineRookBitmap", "outlineRookBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, outlineRookBitmap), XtRString, OUTLINE_ROOK_BITMAP
	}, {
		"outlineKnightBitmap", "outlineKnightBitmap", XtRString,
		sizeof(String), XtOffset(AppDataPtr, outlineKnightBitmap), XtRString,
		OUTLINE_KNIGHT_BITMAP
	}, {
		"outlineBishopBitmap", "outlineBishopBitmap", XtRString,
		sizeof(String), XtOffset(AppDataPtr, outlineBishopBitmap), XtRString,
		OUTLINE_BISHOP_BITMAP
	}, {
		"outlineQueenBitmap", "outlineQueenBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, outlineQueenBitmap), XtRString,
		OUTLINE_QUEEN_BITMAP
	}, {
		"outlineKingBitmap", "outlineKingBitmap", XtRString, sizeof(String),
		XtOffset(AppDataPtr, outlineKingBitmap), XtRString, OUTLINE_KING_BITMAP
	}, {
		"remoteShell", "remoteShell", XtRString, sizeof(String),
		XtOffset(AppDataPtr, remoteShell), XtRString, "rsh"
	}, {
		"timeDelay", "timeDelay", XtRFloat, sizeof(float),
		XtOffset(AppDataPtr, timeDelay), XtRString, (XtPointer) TIME_DELAY
	}, {
		"timeControl", "timeControl", XtRInt, sizeof(int),
		XtOffset(AppDataPtr, timeControl), XtRImmediate,
		(XtPointer) TIME_CONTROL
	}, {
		"saveGameFile", "saveGameFile", XtRString, sizeof(String),
		XtOffset(AppDataPtr, saveGameFile), XtRString, SAVE_GAME_FILE
	}, {
		"readGameFile", "readGameFile", XtRString, sizeof(String),
		XtOffset(AppDataPtr, readGameFile), XtRString, READ_GAME_FILE
	}, {
		"savePositionFile", "savePositionFile", XtRString, sizeof(String),
		XtOffset(AppDataPtr, savePositionFile), XtRString, SAVE_POSITION_FILE
	}, {
		"readPositionFile", "readPositionFile", XtRString, sizeof(String),
		XtOffset(AppDataPtr, readPositionFile), XtRString, READ_POSITION_FILE
	}, {
		"matchMode", "matchMode", XtRString, sizeof(String),
		XtOffset(AppDataPtr, matchMode), XtRString, MATCH_MODE
	}, {
		"monoMode", "monoMode", XtRBoolean, sizeof(Boolean),
		XtOffset(AppDataPtr, monoMode), XtRImmediate, (XtPointer) False
	}, {
		"debugMode", "debugMode", XtRBoolean, sizeof(Boolean),
		XtOffset(AppDataPtr, debugMode), XtRImmediate, (XtPointer) False
	}, {
		"clockMode", "clockMode", XtRBoolean, sizeof(Boolean),
		XtOffset(AppDataPtr, clockMode), XtRImmediate, (XtPointer) True
	}, {
		"bigSizeMode", "bigSizeMode", XtRBoolean, sizeof(Boolean),
		XtOffset(AppDataPtr, bigSizeMode), XtRImmediate, (XtPointer) True
	}, {
		"searchTime","searchTime", XtRInt, sizeof(int),
		XtOffset(AppDataPtr, searchTime), XtRImmediate, (XtPointer) TIME_SEARCH
	}
};

Pixmap *pieceToSolid[] = {
	&solidPawnBitmap, &solidRookBitmap, &solidKnightBitmap,
	&solidBishopBitmap, &solidQueenBitmap, &solidKingBitmap,
	&solidPawnBitmap, &solidRookBitmap, &solidKnightBitmap,
	&solidBishopBitmap, &solidQueenBitmap, &solidKingBitmap
};

Pixmap *pieceToOutline[] = {
	&outlinePawnBitmap, &outlineRookBitmap, &outlineKnightBitmap,
	&outlineBishopBitmap, &outlineQueenBitmap, &outlineKingBitmap,
	&outlinePawnBitmap, &outlineRookBitmap, &outlineKnightBitmap,
	&outlineBishopBitmap, &outlineQueenBitmap, &outlineKingBitmap
};

char pieceToChar[] = {
	'P', 'R', 'N', 'B', 'Q', 'K',
	'p', 'r', 'n', 'b', 'q', 'k', '.'
};

XrmOptionDescRec shellOptions[] = {
	{ "-whitePieceColor", "whitePieceColor", XrmoptionSepArg, NULL },
	{ "-wpc", "whitePieceColor", XrmoptionSepArg, NULL },
	{ "-blackPieceColor", "blackPieceColor", XrmoptionSepArg, NULL },
	{ "-bpc", "blackPieceColor", XrmoptionSepArg, NULL },
	{ "-lightSquareColor", "lightSquareColor", XrmoptionSepArg, NULL },
	{ "-lsc", "lightSquareColor", XrmoptionSepArg, NULL },
	{ "-darkSquareColor", "darkSquareColor", XrmoptionSepArg, NULL },
	{ "-dsc", "darkSquareColor", XrmoptionSepArg, NULL },
	{ "-movesPerSession", "movesPerSession", XrmoptionSepArg, NULL },
	{ "-mps", "movesPerSession", XrmoptionSepArg, NULL },
	{ "-initString", "initString", XrmoptionSepArg, NULL },
	{ "-init", "initString", XrmoptionSepArg, NULL },
	{ "-firstChessProgram", "firstChessProgram", XrmoptionSepArg, NULL },
	{ "-fcp", "firstChessProgram", XrmoptionSepArg, NULL },
	{ "-secondChessProgram", "secondChessProgram", XrmoptionSepArg, NULL },
	{ "-scp", "secondChessProgram", XrmoptionSepArg, NULL },
	{ "-firstHost", "firstHost", XrmoptionSepArg, NULL },
	{ "-fh", "firstHost", XrmoptionSepArg, NULL },
	{ "-secondHost", "secondHost", XrmoptionSepArg, NULL },
	{ "-sh", "secondHost", XrmoptionSepArg, NULL },
	{ "-solidPawnBitmap", "solidPawnBitmap", XrmoptionSepArg, NULL },
	{ "-spb", "solidPawnBitmap", XrmoptionSepArg, NULL },
	{ "-solidRookBitmap", "solidRookBitmap", XrmoptionSepArg, NULL },
	{ "-srb", "solidRookBitmap", XrmoptionSepArg, NULL },
	{ "-solidBishopBitmap", "solidBishopBitmap", XrmoptionSepArg, NULL },
	{ "-sbb", "solidBishopBitmap", XrmoptionSepArg, NULL },
	{ "-solidKnightBitmap", "solidKnightBitmap", XrmoptionSepArg, NULL },
	{ "-skb", "solidKnightBitmap", XrmoptionSepArg, NULL },
	{ "-solidQueenBitmap", "solidQueenBitmap", XrmoptionSepArg, NULL },
	{ "-sqb", "solidQueenBitmap", XrmoptionSepArg, NULL },
	{ "-solidKingBitmap", "solidKingBitmap", XrmoptionSepArg, NULL },
	{ "-skb", "solidKingBitmap", XrmoptionSepArg, NULL },
	{ "-outlinePawnBitmap", "outlinePawnBitmap", XrmoptionSepArg, NULL },
	{ "-opb", "outlinePawnBitmap", XrmoptionSepArg, NULL },
	{ "-outlineRookBitmap", "outlineRookBitmap", XrmoptionSepArg, NULL },
	{ "-orb", "outlineRookBitmap", XrmoptionSepArg, NULL },
	{ "-outlineBishopBitmap", "outlineBishopBitmap", XrmoptionSepArg, NULL },
	{ "-obb", "outlineBishopBitmap", XrmoptionSepArg, NULL },
	{ "-outlineKnightBitmap", "outlineKnightBitmap", XrmoptionSepArg, NULL },
	{ "-okb", "outlineKnightBitmap", XrmoptionSepArg, NULL },
	{ "-outlineQueenBitmap", "outlineQueenBitmap", XrmoptionSepArg, NULL },
	{ "-oqb", "outlineQueenBitmap", XrmoptionSepArg, NULL },
	{ "-outlineKingBitmap", "outlineKingBitmap", XrmoptionSepArg, NULL },
	{ "-okb", "outlineKingBitmap", XrmoptionSepArg, NULL },
	{ "-remoteShell", "remoteShell", XrmoptionSepArg, NULL },
	{ "-rsh", "remoteShell", XrmoptionSepArg, NULL },
	{ "-timeDelay", "timeDelay", XrmoptionSepArg, NULL },
	{ "-td", "timeDelay", XrmoptionSepArg, NULL },
	{ "-timeControl", "timeControl", XrmoptionSepArg, NULL },
	{ "-tc", "timeControl", XrmoptionSepArg, NULL },
	{ "-saveGameFile", "saveGameFile", XrmoptionSepArg, NULL },
	{ "-sgf", "saveGameFile", XrmoptionSepArg, NULL },
	{ "-readGameFile", "readGameFile", XrmoptionSepArg, NULL },
	{ "-rgf", "readGameFile", XrmoptionSepArg, NULL },
	{ "-savePositionFile", "savePositionFile", XrmoptionSepArg, NULL },
	{ "-spf", "savePositionFile", XrmoptionSepArg, NULL },
	{ "-readPositionFile", "readPositionFile", XrmoptionSepArg, NULL },
	{ "-rpf", "readPositionFile", XrmoptionSepArg, NULL },
	{ "-matchMode", "matchMode", XrmoptionSepArg, NULL },
	{ "-mm", "matchMode", XrmoptionSepArg, NULL },
	{ "-monoMode", "monoMode", XrmoptionSepArg, NULL },
	{ "-mono", "monoMode", XrmoptionSepArg, NULL },
	{ "-debugMode", "debugMode", XrmoptionSepArg, NULL },
	{ "-debug", "debugMode", XrmoptionSepArg, NULL },
	{ "-clockMode", "clockMode", XrmoptionSepArg, NULL },
	{ "-clock", "clockMode", XrmoptionSepArg, NULL },
	{ "-bigSizeMode", "bigSizeMode", XrmoptionSepArg, NULL },
	{ "-big", "bigSizeMode", XrmoptionSepArg, NULL },
	{ "-searchTime", "searchTime", XrmoptionSepArg, NULL },
	{ "-st", "searchTime", XrmoptionSepArg, NULL }
};

XtActionsRec boardActions[] = {
	{ "DrawPosition", DrawPosition },
	{ "HandleUserMove", HandleUserMove },
	{ "QuitProc", QuitProc },
	{ "ForwardProc", ForwardProc },
	{ "BackwardProc", BackwardProc },
	{ "PauseProc", PauseProc },
	{ "Iconify", Iconify }
};

char translationsTable[] = "<Expose>: DrawPosition() \n \
							<BtnDown>: HandleUserMove() \n \
							<BtnUp>: HandleUserMove() \n \
							<Key>q: QuitProc() \n \
							<Key>Q: QuitProc() \n \
							<Key>f: ForwardProc() \n \
							<Key>F: ForwardProc() \n \
							<Key>b: BackwardProc() \n \
							<Key>B: BackwardProc() \n \
							<Key>p: PauseProc() \n \
							<Key>P: PauseProc() \n \
							<Key>i: Iconify() \n \
							<Key>I: Iconify() \n \
							<Key>c: Iconify() \n \
							<Key>C: Iconify()";

void
main(argc, argv)
	int argc;
	char **argv;
{
	XSetWindowAttributes window_attributes;
	char buf[MSG_SIZ];
	Arg args[3];
	int length;

	setbuf(stdout, NULL); setbuf(stderr, NULL);

	shellWidget = XtInitialize(argv[0], "XBoard", shellOptions,
		XtNumber(shellOptions), &argc, argv);

	if (argc > 1)
		Usage();

	XtGetApplicationResources(shellWidget, &appData, clientResources,
		XtNumber(clientResources), NULL, 0);

	/*
	 * Determine matchMode state -- poor man's resource converter
	 */
	if (strcmp(appData.matchMode, "Init") == 0)
		matchMode = MatchInit;
	else if (strcmp(appData.matchMode, "Position") == 0)
		matchMode = MatchPosition;
	else if (strcmp(appData.matchMode, "Opening") == 0)
		matchMode = MatchOpening;
	else if (strcmp(appData.matchMode, "False") == 0)
		matchMode = MatchFalse;
	else {
		fprintf(stderr, "xboard: bad matchMode option %s\n", appData.matchMode);
		Usage();
	}

	xDisplay = XtDisplay(shellWidget);
	xScreen = DefaultScreen(xDisplay);

	if (!appData.bigSizeMode) {
		squareSize = SMALL_SQUARE_SIZE;
		XtSetArg(boardArgs[1], XtNwidth,
			LINE_GAP + BOARD_SIZE * (SMALL_SQUARE_SIZE + LINE_GAP));
		XtSetArg(boardArgs[2], XtNheight,
			LINE_GAP + BOARD_SIZE * (SMALL_SQUARE_SIZE + LINE_GAP));
	}

	/*
	 * Detect if there are not enough colors are available and adapt.
	 */
	if (DefaultDepth(xDisplay, xScreen) <= 2)
		appData.monoMode = True;

	/*
	 * widget hierarchy
	 */
	formWidget = XtCreateManagedWidget("form",
		formWidgetClass, shellWidget, NULL, 0);

		widgetList[0] = whiteTimerWidget = XtCreateWidget("white time:",
			labelWidgetClass, formWidget, timerArgs, XtNumber(timerArgs));

		widgetList[1] = blackTimerWidget = XtCreateWidget("black time:",
			labelWidgetClass, formWidget, timerArgs, XtNumber(timerArgs));

		widgetList[2] = messageWidget = XtCreateWidget("message",
			labelWidgetClass, formWidget, messageArgs, XtNumber(messageArgs));

		widgetList[3] = commandsWidget = XtCreateWidget("commands",
			listWidgetClass, formWidget, commandsArgs, XtNumber(commandsArgs));

		widgetList[4] = boardWidget = XtCreateWidget("board",
			widgetClass, formWidget, boardArgs, XtNumber(boardArgs));

	XtManageChildren(widgetList, XtNumber(widgetList));

	/*
	 * Calculate the width of the timer labels.
	 */
	XtSetArg(args[0], XtNfont, &labelFont);
	XtGetValues(whiteTimerWidget, args, 1);
	if (appData.clockMode) {
		sprintf(buf, "White: %s ", TimeString(appData.timeControl * 60));
		length = XTextWidth(labelFont, buf, strlen(buf) - 1);
	} else
		length = XTextWidth(labelFont, "White  ", 7);
	XtSetArg(args[0], XtNwidth, length);
	XtSetValues(whiteTimerWidget, args, 1);
	XtSetValues(blackTimerWidget, args, 1);

	/*
	 * formWidget uses these constraints but they are stored in the children.
	 */
	XtSetArg(args[0], XtNfromHoriz, whiteTimerWidget);
	XtSetValues(blackTimerWidget, args, 1);
	XtSetArg(args[0], XtNfromVert, whiteTimerWidget);
	XtSetArg(args[1], XtNbackground, XBlackPixel(xDisplay, xScreen));
	XtSetArg(args[2], XtNforeground, XWhitePixel(xDisplay, xScreen));
	XtSetValues(messageWidget, args, 3);
	XtSetArg(args[0], XtNfromVert, messageWidget);
	XtSetValues(commandsWidget, args, 1);
	XtSetArg(args[0], XtNfromVert, commandsWidget);
	XtSetValues(boardWidget, args, 1);

	XtRealizeWidget(shellWidget);

	xBoardWindow = XtWindow(boardWidget);

	/*
	 * Create an icon.
	 */
	iconPixmap = XCreateBitmapFromData(xDisplay, XtWindow(shellWidget),
		icon_bits, icon_width, icon_height);
	XtSetArg(args[0], XtNiconPixmap, iconPixmap);
	XtSetValues(shellWidget, args, 1);

	/*
	 * Create a cursor for the board widget.
	 */
	window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
	XChangeWindowAttributes(xDisplay, xBoardWindow,
		CWCursor, &window_attributes);

	/*
	 * Inhibit shell resizing.
	 */
	XtGetValues(shellWidget, shellArgs, 2);
	shellArgs[4].value = shellArgs[2].value = shellArgs[0].value;
	shellArgs[5].value = shellArgs[3].value = shellArgs[1].value;
	XtSetValues(shellWidget, &shellArgs[2], 4);

	CreateGCs();
	CreateGrid();
	CreatePieces();

	XtAddCallback(commandsWidget, XtNcallback, SelectCommand, NULL);
	XtAddActions(boardActions, XtNumber(boardActions));
	XtOverrideTranslations(boardWidget,
		XtParseTranslationTable(translationsTable));

	DisplayMessage("");

	/*
	 * If there is to be a machine match, set it up.
	 */
	if (matchMode != MatchFalse)
		TwoMachinesPlayProc();
	else
		ResetProc();

	XtMainLoop();
}

void
CreateGCs()
{
	XtGCMask value_mask = GCLineWidth | GCLineStyle | GCForeground
		| GCBackground | GCFunction | GCPlaneMask;
	XGCValues gc_values;

	gc_values.plane_mask = AllPlanes;
	gc_values.line_width = LINE_GAP;
	gc_values.line_style = LineSolid;
	gc_values.function = GXcopy;

	gc_values.foreground = XBlackPixel(xDisplay, xScreen);
	gc_values.background = XBlackPixel(xDisplay, xScreen);
	lineGC = XtGetGC(shellWidget, value_mask, &gc_values);

	if (appData.monoMode) {
		gc_values.foreground = XWhitePixel(xDisplay, xScreen);
		gc_values.background = XBlackPixel(xDisplay, xScreen);
		lightSquareGC = wbPieceGC
			= XtGetGC(shellWidget, value_mask, &gc_values);

		gc_values.foreground = XBlackPixel(xDisplay, xScreen);
		gc_values.background = XWhitePixel(xDisplay, xScreen);
		darkSquareGC = bwPieceGC
			= XtGetGC(shellWidget, value_mask, &gc_values);
	} else {
		gc_values.foreground = appData.lightSquareColor;
		gc_values.background = appData.darkSquareColor;
		lightSquareGC = XtGetGC(shellWidget, value_mask, &gc_values);

		gc_values.foreground = appData.darkSquareColor;
		gc_values.background = appData.lightSquareColor;
		darkSquareGC = XtGetGC(shellWidget, value_mask, &gc_values);

		gc_values.foreground = appData.whitePieceColor;
		gc_values.background = appData.darkSquareColor;
		wdPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);

		gc_values.foreground = appData.whitePieceColor;
		gc_values.background = appData.lightSquareColor;
		wlPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);

		gc_values.foreground = appData.blackPieceColor;
		gc_values.background = appData.darkSquareColor;
		bdPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);

		gc_values.foreground = appData.blackPieceColor;
		gc_values.background = appData.lightSquareColor;
		blPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
	}
}

void
CreatePieces()
{
	XSynchronize(xDisplay, True);	/* Work-around for xlib/xt buffering bug */

	ReadBitmap(appData.solidPawnBitmap, &solidPawnBitmap,
		solid_pawn_bits, pawn_small_bits);
	ReadBitmap(appData.solidRookBitmap, &solidRookBitmap,
		solid_rook_bits, rook_small_bits);
	ReadBitmap(appData.solidKnightBitmap, &solidKnightBitmap,
		solid_knight_bits, knight_small_bits);
	ReadBitmap(appData.solidBishopBitmap, &solidBishopBitmap,
		solid_bishop_bits, bishop_small_bits);
	ReadBitmap(appData.solidQueenBitmap, &solidQueenBitmap,
		solid_queen_bits, queen_small_bits);
	ReadBitmap(appData.solidKingBitmap, &solidKingBitmap,
		solid_king_bits, king_small_bits);

	if (appData.monoMode) {
		ReadBitmap(appData.outlinePawnBitmap, &outlinePawnBitmap,
			outline_pawn_bits, pawn_small_outline_bits);
		ReadBitmap(appData.outlineRookBitmap, &outlineRookBitmap,
			outline_rook_bits, rook_small_outline_bits);
		ReadBitmap(appData.outlineKnightBitmap, &outlineKnightBitmap,
			outline_knight_bits, knight_small_outline_bits);
		ReadBitmap(appData.outlineBishopBitmap, &outlineBishopBitmap,
			outline_bishop_bits, bishop_small_outline_bits);
		ReadBitmap(appData.outlineQueenBitmap, &outlineQueenBitmap,
			outline_queen_bits, queen_small_outline_bits);
		ReadBitmap(appData.outlineKingBitmap, &outlineKingBitmap,
			outline_king_bits, king_small_outline_bits);
	}

	XSynchronize(xDisplay, False);	/* Work-around for xlib/xt buffering bug */
}

void
ReadBitmap(name, pm, big_bits, small_bits)
	String name;
	Pixmap *pm;
	char big_bits[], small_bits[];
{
	int x_hot, y_hot;
	u_int w, h;

	if (XReadBitmapFile(xDisplay, xBoardWindow, name,
		&w, &h, pm, &x_hot, &y_hot) != BitmapSuccess
		|| w != squareSize || h != squareSize) {
		if (appData.bigSizeMode)
			*pm = XCreateBitmapFromData(xDisplay, xBoardWindow,
				big_bits, squareSize, squareSize);
		else *pm = XCreateBitmapFromData(xDisplay, xBoardWindow,
			small_bits, squareSize, squareSize);
	}
}

void
CreateGrid()
{
	int i;

	for (i = 0; i < BOARD_SIZE + 1; i++) {
		gridSegments[i].x1 = 0;
		gridSegments[i].y1 = LINE_GAP / 2 + (i * (squareSize + LINE_GAP));
		gridSegments[i].x2 = LINE_GAP + BOARD_SIZE * (squareSize + LINE_GAP);
		gridSegments[i].y2 = LINE_GAP / 2 + (i * (squareSize + LINE_GAP));
		gridSegments[i + BOARD_SIZE + 1].x1 = LINE_GAP / 2
			+ (i * (squareSize + LINE_GAP));
		gridSegments[i + BOARD_SIZE + 1].y1 = 0;
		gridSegments[i + BOARD_SIZE + 1].x2 = LINE_GAP / 2
			+ (i * (squareSize + LINE_GAP));
		gridSegments[i + BOARD_SIZE + 1].y2 =
			BOARD_SIZE * (squareSize + LINE_GAP);
	}
}

/*
 * If the user selects on a border boundary or off the board, return failure.
 * Otherwise map the event coordinate to the square.
 */
int
EventToSquare(x)
	int x;
{
	if (x < LINE_GAP)
		return -1;
	x -= LINE_GAP;
	if ((x % (squareSize + LINE_GAP)) >= squareSize)
		return -1;
	x /= (squareSize + LINE_GAP);
	if (x >= BOARD_SIZE)
		return -1;
	return x;
}

int
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;
	}
}

void
DrawSquare(row, column, piece)
	int row, column, piece;
{
	int square_color, x, y;

	if (flipView)
		column = (BOARD_SIZE - 1) - column;
	else
		row = (BOARD_SIZE - 1) - row;

	square_color = ((column % 2 == 1) && (row % 2 == 1))
		|| ((column % 2 == 0) && (row % 2 == 0));

	x = LINE_GAP + column * (squareSize + LINE_GAP);
	y = LINE_GAP + row * (squareSize + LINE_GAP);

	if (piece == EmptySquare)
		XFillRectangle(xDisplay, xBoardWindow, square_color
			? lightSquareGC : darkSquareGC, x, y, squareSize, squareSize);
	else if (appData.monoMode) {
		if (square_color)
			XCopyPlane(xDisplay, piece < BlackPawn
				? *pieceToOutline[piece] : *pieceToSolid[piece], xBoardWindow,
				bwPieceGC, 0, 0, squareSize, squareSize, x, y, 1);
		else XCopyPlane(xDisplay, piece < BlackPawn
				? *pieceToSolid[piece] : *pieceToOutline[piece], xBoardWindow,
				wbPieceGC, 0, 0, squareSize, squareSize, x, y, 1);
	} else {
		if (square_color)
			XCopyPlane(xDisplay, *pieceToSolid[piece],
				xBoardWindow, piece < BlackPawn ? wlPieceGC : blPieceGC,
				0, 0, squareSize, squareSize, x, y, 1);
		else XCopyPlane(xDisplay, *pieceToSolid[piece],
				xBoardWindow, piece < BlackPawn ? wdPieceGC : bdPieceGC,
				0, 0, squareSize, squareSize, x, y, 1);
	}
}

/*
 * event handler for redrawing the board
 */
void
DrawPosition(w, event)
	Widget w;
	XExposeEvent *event;
{
	Arg args[1];
	int i, j;
	Boolean iconic;

	/*
	 * We probably shouldn't have to do this, but R4 Xt gets confused.
	 */
	XtSetArg(args[0], XtNiconic, &iconic);
	XtGetValues(shellWidget, args, 1);
	if (iconic == False) {
		XtSetArg(args[0], XtNiconic, False);
		XtSetValues(shellWidget, args, 1);
	}

	/*
	 * It would be simpler to clear the window with XClearWindow()
	 * but this causes a very distracting flicker.
	 */
	XDrawSegments(xDisplay, xBoardWindow, lineGC,
		gridSegments, (BOARD_SIZE + 1) * 2);

	for (i = 0; i < BOARD_SIZE; i++)
		for (j = 0; j < BOARD_SIZE; j++)
			DrawSquare(i, j, (int) boards[currentMove][i][j]);

	XSync(xDisplay, False);
}

void
InitPosition()
{
	currentMove = forwardMostMove = 0;
	CopyBoard(boards[0], initialPosition);
	DrawPosition(boardWidget, (XExposeEvent *) NULL);
}

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];
}

void
SendCurrentBoard(fp)
	FILE *fp;
{
	char message[MSG_SIZ];
	u_char *bp;
	int i, j;

	SendToProgram("edit\n", fp);
	SendToProgram("#\n", fp);
	for (i = BOARD_SIZE - 1; i >= 0; i--) {
		bp = &boards[currentMove][i][0];
		for (j = 0; j < BOARD_SIZE; j++, bp++) {
			if (*bp < BlackPawn) {
				sprintf(message, "%c%c%c\n", pieceToChar[*bp],
					'a' + j, '1' + i);
				SendToProgram(message, fp);
			}
		}
	}

	SendToProgram("c\n", fp);
	for (i = BOARD_SIZE - 1; i >= 0; i--) {
		bp = &boards[currentMove][i][0];
		for (j = 0; j < BOARD_SIZE; j++, bp++) {
			if ((*bp != EmptySquare) && (*bp >= BlackPawn)) {
				sprintf(message, "%c%c%c\n", pieceToChar[*bp - BlackPawn],
					'a' + j, '1' + i);
				SendToProgram(message, fp);
			}
		}
	}

	SendToProgram(".\n", fp);
}

/*
 * event handler for parsing user moves
 */
void
HandleUserMove(w, event)
	Widget w;
	XEvent *event;
{
	int move_type, to_x, to_y;
	char user_move[MSG_SIZ];

	if ((w != boardWidget) || (matchMode != MatchFalse))
		return;

	switch (gameMode) {
	case EndOfGame:
	case PlayFromGameFile:
		return;
	case MachinePlaysWhite:
		if ((currentMove % 2) == 0)
			return;
		break;
	case MachinePlaysBlack:
		if ((currentMove % 2) == 1)
			return;
		break;
	default:
		break;
	}

	switch (event->type) {
	case ButtonPress:
		if ((fromX >= 0) || (fromY >= 0))
			return;
		if ((fromX = EventToSquare(event->xbutton.x)) < 0)
			break;
		if ((fromY = EventToSquare(event->xbutton.y)) < 0)
			break;
		if (flipView)
			fromX = BOARD_SIZE - 1 - fromX;
		else
			fromY = BOARD_SIZE - 1 - fromY;
		break;
	case ButtonRelease:
		if ((fromX < 0) || (fromY < 0))
			return;
		if ((to_x = EventToSquare(event->xbutton.x)) < 0)
			break;
		if ((to_y = EventToSquare(event->xbutton.y)) < 0)
			break;
		if (flipView)
			to_x = BOARD_SIZE - 1 - to_x;
		else
			to_y = BOARD_SIZE - 1 - to_y;

		MakeMove(&move_type, fromX, fromY, to_x, to_y);

		switch (move_type) {
		case WhitePromotion:
		case BlackPromotion:	/* GNU Chess limitation of only Q promotion */
			sprintf(user_move, "%c%c%c%c(Q)\n", /* GNU Chess limitation */
				'a' + fromX, '1' + fromY, 'a' + to_x, '1' + to_y);
			break;
		case NormalMove:
		case WhiteKingSideCastle:
		case WhiteQueenSideCastle:
		case WhiteCapturesEnPassant:
		case BlackKingSideCastle:
		case BlackQueenSideCastle:
		case BlackCapturesEnPassant:
			sprintf(user_move, "%c%c%c%c\n",
				'a' + fromX, '1' + fromY, 'a' + to_x, '1' + to_y);
			break;
		}

		fromX = fromY = -1;

		SendToProgram(user_move, toFirstProgFP);
		strcpy(moveList[currentMove - 1], user_move);

		switch (gameMode) {
		case PauseGame:
			PauseProc();	/* a user move restarts a paused game */
			break;
		case ForceMoves:
			forwardForce = False;
			break;
		case BeginningOfGame:
		case SetupPosition:
			lastGameMode = gameMode = MachinePlaysBlack;
		case MachinePlaysBlack:
			/*
			 * gnuchess prefers to be told that it is black when it is on move.
			 */
			if (firstMove) {
				firstMove = False;
				if (currentMove > 1)	/* If it is being forced */
					SendToProgram("black\n", toFirstProgFP);
			}
		case MachinePlaysWhite:
			break;
		}
		break;
	}
}

void
HandleMachineMove(message)
	char *message;
{
	char machine_move[MSG_SIZ], buf1[MSG_SIZ], buf2[MSG_SIZ];
	int i, j, move_type, from_x, from_y, to_x, to_y;

	if (strncmp(message, "warning:", 8) == 0) {
		DisplayMessage(message);
		return;
	}

	/*
	 * If either host fails, reset to using localhost for both.
	 * Display an error message.
	 */
	if ((StrStr(message, "unknown host") != NULL)
		|| (StrStr(message, "No remote directory") != NULL)
		|| (StrStr(message, "not found") != NULL)) {
		ShutdownChessPrograms("");
		strcpy(appData.firstHost, "localhost");
		strcpy(appData.secondHost, "localhost");
		if (matchMode != MatchFalse)
			TwoMachinesPlayProc();
		else
			ResetProc();
		strcpy(buf1, "using localhost - ");
		strcat(buf1, message);
		DisplayMessage(buf1);
		return;
	}

	/*
	 * If the move is illegal, cancel it and redraw the board.
	 */
	if (strncmp(message, "Illegal move", 12) == 0) {
		if (gameMode == PlayFromGameFile) {
			lastGameMode = gameMode;
			gameMode = BeginningOfGame;
			sprintf(buf1, "Illegal move: %s", moveList[currentMove - 1]);
			ShutdownChessPrograms(buf1);
			return;
		}

		currentMove--;
		DisplayClocks(DisplayTimers);
		DisplayMessage(message);

		/*
		 * Only redraw the squares that have changed.
		 */
		for (i = 0; i < BOARD_SIZE; i++)
			for (j = 0; j < BOARD_SIZE; j++)
				if (boards[currentMove][i][j] != boards[currentMove + 1][i][j])
					DrawSquare(i, j, (int) boards[currentMove][i][j]);

		XSync(xDisplay, False);
		return;
	}

	if (strncmp(message, "Hint:", 5) == 0) {
		DisplayMessage(message);
		return;
	}

	/*
	 * win, lose or draw
	 */
	if (strncmp(message, "White", 5) == 0) {
		ShutdownChessPrograms("White wins");
		return;
	} else if (strncmp(message, "Black", 5) == 0) {
		ShutdownChessPrograms("Black wins");
		return;
	} else if (strncmp(message, "Draw", 4) == 0) {
		ShutdownChessPrograms("Draw");
		return;
	}

	/*
	 * normal machine reply move
	 */
	if (StrStr(message, "...") != NULL) {
		sscanf(message, "%s %s %s", buf1, buf2, machine_move);
		if (machine_move[0] == '\0')
			return;
	} else
		return; /* ignore noise */

	strcpy(moveList[currentMove], machine_move);

	from_x = machine_move[0] - 'a';
	from_y = machine_move[1] - '1';
	to_x = machine_move[2] - 'a';
	to_y = machine_move[3] - '1';

	MakeMove(&move_type, from_x, from_y, to_x, to_y);

	switch (gameMode) {
	case PauseGame:
	case SetupPosition:
		break;
	case ForceMoves:
	case PlayFromGameFile:
		strncat(machine_move, "\n", 1);
		SendToProgram(machine_move, toFirstProgFP);
		break;
	case TwoMachinesPlay:
		strncat(machine_move, "\n", 1);
		SendToProgram(machine_move, currentMove % 2
			? toFirstProgFP : toSecondProgFP);
		/*
		 * gnuchess prefers to be told that it is black when it is on move.
		 */
		if (firstMove) {
			firstMove = False;
			SendToProgram("black\n", toFirstProgFP);
		}
		break;
	}
}

void
ReadGameFile()
{
	static char move_number[MSG_SIZ], white_move[MSG_SIZ], black_move[MSG_SIZ];
	int move_type, from_x, from_y, to_x, to_y;
	char move[MSG_SIZ];

	if ((gameMode == EndOfGame) || (gameMode == BeginningOfGame)) {
		fclose(gameFileFP);
		gameFileFP = NULL;
		return;
	}

next_move:
	if (currentMove % 2 == 0) {
next_line:
		if (fgets(move, sizeof(move), gameFileFP) == (char *) NULL) {
			lastGameMode = gameMode = MachinePlaysBlack;
end_parse:
			if (readGameXID != NULL) {
				XtRemoveTimeOut(readGameXID);
				readGameXID = NULL;
			}
			fclose(gameFileFP);
			gameFileFP = NULL;
			return;
		}

		if ((move[0] == '%') || (move[0] == '!'))
			goto next_line;

		move_number[0] = white_move[0] = black_move[0] = NULL;

		if (sscanf(move, "%s %s %s", move_number,
			white_move, black_move) == EOF)
			goto end_parse;

		if (white_move[0] == '\0') {
			if (move_number[0] != '\0')
				strcpy(white_move, move_number);
			else {
				DisplayMessage("End of Game");
				goto end_parse;
			}
		}

		if ((strcmp(white_move, "draw") == 0)
			|| (strcmp(white_move, "Draw") == 0)
			|| (strcmp(white_move, "DRAW") == 0)
			|| (strcmp(white_move, "Resign") == 0)
			|| (strcmp(white_move, "resign") == 0)
			|| (strcmp(white_move, "RESIGN") == 0)
			|| (strcmp(white_move, "0-1") == 0)
			|| (strcmp(white_move, "1-0") == 0)) {
			DisplayMessage(white_move);
			lastGameMode = gameMode;
			gameMode = BeginningOfGame;
			goto end_parse;
		} if (strcmp(white_move, "o-o-o") == 0) {
			from_x = 4;
			from_y = 0;
			to_x = 2;
			to_y = 0;
		} else if (strcmp(white_move, "o-o") == 0) {
			from_x = 4;
			from_y = 0;
			to_x = 6;
			to_y = 0;
		} else {
			from_x = white_move[0] - 'a';
			from_y = white_move[1] - '1';
			to_x = white_move[2] - 'a';
			to_y = white_move[3] - '1';
		}

		strncat(white_move, "\n", 1);
		SendToProgram(white_move, toFirstProgFP);
		strcpy(moveList[currentMove], white_move);
	} else {
		if (black_move[0] == '\0') {
			DisplayMessage("End of Game");
			goto end_parse;
		} if ((strcmp(black_move, "draw") == 0)
			|| (strcmp(black_move, "Draw") == 0)
			|| (strcmp(black_move, "DRAW") == 0)
			|| (strcmp(black_move, "Resign") == 0)
			|| (strcmp(black_move, "resign") == 0)
			|| (strcmp(black_move, "RESIGN") == 0)
			|| (strcmp(black_move, "0-1") == 0)
			|| (strcmp(black_move, "1-0") == 0)) {
			DisplayMessage(black_move);
			lastGameMode = gameMode;
			gameMode = BeginningOfGame;
			goto end_parse;
		} if (strcmp(black_move, "o-o-o") == 0) {
			from_x = 4;
			from_y = 7;
			to_x = 2;
			to_y = 7;
		} else if (strcmp(black_move, "o-o") == 0) {
			from_x = 4;
			from_y = 7;
			to_x = 6;
			to_y = 7;
		} else {
			from_x = black_move[0] - 'a';
			from_y = black_move[1] - '1';
			to_x = black_move[2] - 'a';
			to_y = black_move[3] - '1';
		}

		strncat(black_move, "\n", 1);
		SendToProgram(black_move, toFirstProgFP);
		strcpy(moveList[currentMove], black_move);
	}

	MakeMove(&move_type, from_x, from_y, to_x, to_y);

	if (matchMode == MatchOpening)
		goto next_move;

	readGameXID = XtAddTimeOut((int) (1000 * appData.timeDelay),
		ReadGameFile, NULL);
}

/*
 * MakeMove() displays moves.  If they are illegal, GNU chess will detect
 * this and send an Illegal move message.  XBoard will then retract the move.
 * The clockMode False case is tricky because it displays the player on move.
 */
void
MakeMove(move_type, from_x, from_y, to_x, to_y)
	int *move_type, from_x, from_y, to_x, to_y;
{
	char message[MSG_SIZ];
	int move;

	if ((gameMode != PlayFromGameFile)
		&& (gameMode != ForceMoves) && appData.clockMode)
		DisplayClocks(DisplayTimers);

	CopyBoard(boards[currentMove + 1], boards[currentMove]);
	forwardMostMove = ++currentMove;

	move = (currentMove + 1) / 2;

	if (!appData.clockMode)
		DisplayClocks(DisplayTimers);

	if (from_y == 0 && from_x == 4				/* white king-side castle */
			&& boards[currentMove][from_y][from_x] == WhiteKing
			&& to_y == 0 && to_x == 6) {
		*move_type = WhiteKingSideCastle;
		boards[currentMove][0][7] = EmptySquare;
		boards[currentMove][0][6] = WhiteKing;
		boards[currentMove][0][5] = WhiteRook;
		boards[currentMove][0][4] = EmptySquare;
		DrawSquare(0, 7, (int) boards[currentMove][0][7]);
		DrawSquare(0, 6, (int) boards[currentMove][0][6]);
		DrawSquare(0, 5, (int) boards[currentMove][0][5]);
		DrawSquare(0, 4, (int) boards[currentMove][0][4]);
		sprintf(message, "%d. 0-0", move);
	} else if (from_y == 0 && from_x == 4		/* white queen-side castle */
			&& boards[currentMove][from_y][from_x] == WhiteKing
			&& to_y == 0 && to_x == 2) {
		*move_type = WhiteQueenSideCastle;
		boards[currentMove][0][0] = EmptySquare;
		boards[currentMove][0][2] = WhiteKing;
		boards[currentMove][0][3] = WhiteRook;
		boards[currentMove][0][4] = EmptySquare;
		DrawSquare(0, 0, (int) boards[currentMove][0][0]);
		DrawSquare(0, 2, (int) boards[currentMove][0][2]);
		DrawSquare(0, 3, (int) boards[currentMove][0][3]);
		DrawSquare(0, 4, (int) boards[currentMove][0][4]);
		sprintf(message, "%d. 0-0-0", move);
	} else if (from_y == 6						/* white pawn promotion */
			&& boards[currentMove][from_y][from_x] == WhitePawn && to_y == 7) {
		*move_type = WhitePromotion;
		boards[currentMove][6][from_x] = EmptySquare;
		boards[currentMove][7][to_x] = WhiteQueen;
		DrawSquare(6, from_x, (int) boards[currentMove][6][from_x]);
		DrawSquare(7, to_x, (int) boards[currentMove][7][to_x]);
		sprintf(message, "%d. %c8(Q)", move, from_x + 'a');
	} else if ((from_y == 4)					/* white captures en passant */
			&& (to_x != from_x)
			&& (boards[currentMove][from_y][from_x] == WhitePawn)
			&& (boards[currentMove][to_y][to_x] == EmptySquare)) {
		*move_type = WhiteCapturesEnPassant;
		boards[currentMove][from_y][from_x] = EmptySquare;
		boards[currentMove][to_y][to_x] = WhitePawn;
		boards[currentMove][to_y - 1][to_x] = EmptySquare;
		DrawSquare(from_y, from_x, (int) boards[currentMove][from_y][from_x]);
		DrawSquare(to_y, to_x, (int) boards[currentMove][to_y][to_x]);
		DrawSquare(to_y - 1, to_x, (int) boards[currentMove][to_y - 1][to_x]);
		sprintf(message, "%d. %c%c ep", move, from_x + 'a', to_x + 'a');
	} else if (from_y == 7 && from_x == 4		/* black king-side castle */
			&& boards[currentMove][from_y][from_x] == BlackKing
			&& to_y == 7 && to_x == 6) {
		*move_type = BlackKingSideCastle;
		boards[currentMove][7][4] = EmptySquare;
		boards[currentMove][7][5] = BlackRook;
		boards[currentMove][7][6] = BlackKing;
		boards[currentMove][7][7] = EmptySquare;
		DrawSquare(7, 7, (int) boards[currentMove][7][7]);
		DrawSquare(7, 6, (int) boards[currentMove][7][6]);
		DrawSquare(7, 5, (int) boards[currentMove][7][5]);
		DrawSquare(7, 4, (int) boards[currentMove][7][4]);
		sprintf(message, "%d. ... 0-0", move);
	} else if (from_y == 7 && from_x == 4		/* black queen-side castle */
			&& boards[currentMove][from_y][from_x] == BlackKing
			&& to_y == 7 && to_x == 2) {
		*move_type = BlackQueenSideCastle;
		boards[currentMove][7][0] = EmptySquare;
		boards[currentMove][7][2] = BlackKing;
		boards[currentMove][7][3] = BlackRook;
		boards[currentMove][7][4] = EmptySquare;
		DrawSquare(7, 0, (int) boards[currentMove][7][0]);
		DrawSquare(7, 2, (int) boards[currentMove][7][2]);
		DrawSquare(7, 3, (int) boards[currentMove][7][3]);
		DrawSquare(7, 4, (int) boards[currentMove][7][4]);
		sprintf(message, "%d. ... 0-0-0", move);
	} else if (from_y == 1						/* black pawn promotion */
			&& boards[currentMove][from_y][from_x] == BlackPawn && to_y == 0) {
		*move_type = BlackPromotion;
		boards[currentMove][1][from_x] = EmptySquare;
		boards[currentMove][0][to_x] = BlackQueen;
		DrawSquare(1, from_x, (int) boards[currentMove][1][from_x]);
		DrawSquare(0, to_x, (int) boards[currentMove][0][to_x]);
		sprintf(message, "%d. ... %c8(Q)", move, from_x + 'a');
	} else if ((from_y == 3)					/* black captures en passant */
			&& (to_x != from_x)
			&& (boards[currentMove][from_y][from_x] == BlackPawn)
			&& (boards[currentMove][to_y][to_x] == EmptySquare)) {
		*move_type = BlackCapturesEnPassant;
		boards[currentMove][from_y][from_x] = EmptySquare;
		boards[currentMove][to_y][to_x] = BlackPawn;
		boards[currentMove][to_y + 1][to_x] = EmptySquare;
		DrawSquare(from_y, from_x, (int) boards[currentMove][from_y][from_x]);
		DrawSquare(to_y, to_x, (int) boards[currentMove][to_y][to_x]);
		DrawSquare(to_y + 1, to_x, (int) boards[currentMove][to_y + 1][to_x]);
		sprintf(message, "%d. ... %c%c ep", move, from_x + 'a', to_x + 'a');
	} else {
		*move_type = NormalMove;
		boards[currentMove][to_y][to_x] = boards[currentMove][from_y][from_x];
		boards[currentMove][from_y][from_x] = EmptySquare;
		DrawSquare(to_y, to_x, (int) boards[currentMove][to_y][to_x]);
		DrawSquare(from_y, from_x, (int) boards[currentMove][from_y][from_x]);
		sprintf(message, "%d. %s%c%c%c%c", move, currentMove % 2 ?
			"" : "... ", from_x + 'a', from_y + '1', to_x + 'a', to_y + '1');
	}

	DisplayMessage(message);
	XSync(xDisplay, False);
}

void
InitChessProgram(host_name, program_name, pid, to, from, xid)
	char *host_name, *program_name;
	int *pid;
	FILE **to, **from;
	XtIntervalId *xid;
{
	char time_control[10], moves_per_session[10], time_search[10];
	int to_prog[2], from_prog[2];
	FILE *from_fp, *to_fp;

	signal(SIGPIPE, CatchPipeSignal);
	pipe(to_prog);
	pipe(from_prog);

	if ((*pid = fork()) == 0) {
		signal(SIGPIPE, CatchPipeSignal);
		dup2(to_prog[0], 0);
		dup2(from_prog[1], 1);
		close(to_prog[0]);
		close(to_prog[1]);
		close(from_prog[0]);
		close(from_prog[1]);
		dup2(1, fileno(stderr));	/* force stderr to the pipe */
		sprintf(time_control, "%d", appData.timeControl);
		sprintf(time_search, "%d", appData.searchTime);
		sprintf(moves_per_session, "%d", appData.movesPerSession);
		if (strcmp(host_name, "localhost") == 0) {
			if (appData.searchTime <= 0)
				execlp(program_name, program_name,
					moves_per_session, time_control, (char *) NULL);
			else
				execlp(program_name, program_name, time_search, (char *) NULL);
		} else {
			if (appData.searchTime <= 0)
				execlp(appData.remoteShell, appData.remoteShell,
					host_name, program_name, moves_per_session,
					time_control, (char *) NULL);
			else
				execlp(appData.remoteShell, appData.remoteShell,
					host_name, program_name, time_search, (char *) NULL);
		}

		perror(program_name);
		exit(1);
	}

	close(to_prog[0]);
	close(from_prog[1]);

	*from = from_fp = fdopen(from_prog[0], "r");
	*to = to_fp = fdopen(to_prog[1], "w");
	setbuf(from_fp, NULL); setbuf(to_fp, NULL);

	*xid = XtAddInput(fileno(from_fp), XtInputReadMask,
		ReceiveFromProgram, from_fp);

	SendToProgram(appData.initString, to_fp);
}

void
ShutdownChessPrograms(message)
	char *message;
{
	lastGameMode = gameMode;
	gameMode = EndOfGame;
	DisplayMessage(message);

	if (firstProgramPID != 0) {
		fclose(fromFirstProgFP);
		fclose(toFirstProgFP);
		fromFirstProgFP = toFirstProgFP = NULL;
		kill(firstProgramPID, SIGTERM);
	}

	if (secondProgramPID != 0) {
		fclose(fromSecondProgFP);
		fclose(toSecondProgFP);
		fromSecondProgFP = toSecondProgFP = NULL;
		kill(secondProgramPID, SIGTERM);
	}

	if (firstProgramXID != NULL)
		XtRemoveInput(firstProgramXID);
	if (secondProgramXID != NULL)
		XtRemoveInput(secondProgramXID);
	if (readGameXID != NULL)
		XtRemoveTimeOut(readGameXID);

	firstProgramXID = secondProgramXID = readGameXID = NULL;
	firstProgramPID = secondProgramPID = 0;

	DisplayClocks(StopTimers);
}

void
SelectCommand(w, client_data, call_data)
	Widget w;
	XtPointer client_data, call_data;
{
	XawListReturnStruct *list_return = XawListShowCurrent(w);

	fromX = fromY = -1;

	if ((gameMode == PauseGame) && (list_return->list_index != ButtonPause))
		PauseProc();

	switch (list_return->list_index) {
	case ButtonQuit:
		QuitProc();
		break;
	case ButtonBackward:
		BackwardProc();
		break;
	case ButtonForward:
		ForwardProc();
		break;
	case ButtonFlip:
		FlipProc();
		break;
	case ButtonReset:
		ResetProc();
		break;
	case ButtonSaveGame:
		SaveGameProc();
		break;
	case ButtonSavePosition:
		SavePositionProc();
		break;
	case ButtonHint:
		HintProc();
		break;
	case ButtonSwitch:
		SwitchProc();
		break;
	case ButtonSetupFromFile:
		(void) SetupPositionFromFileProc();
		break;
	case ButtonPlayFromFile:
		(void) PlayFromGameFileProc();
		break;
	case ButtonMachinePlaysBlack:
		MachinePlaysBlackProc();
		break;
	case ButtonMachinePlaysWhite:
		MachinePlaysWhiteProc();
		break;
	case ButtonTwoMachinesPlay:
		TwoMachinesPlayProc();
		break;
	case ButtonForce:
		ForceProc();
		break;
	case ButtonPause:
		PauseProc();
		break;
	}

	XawListUnhighlight(w);
}

/*
 * Button procedures in order.
 */
void
QuitProc()
{
	ShutdownChessPrograms("Quitting");
	exit(0);
}

int
PlayFromGameFileProc()
{
	char buf[MSG_SIZ];

	if (gameMode != BeginningOfGame)
		return False;

	if ((gameFileFP = fopen(appData.readGameFile, "r")) == NULL) {
		sprintf(buf, "Can't open %s", appData.readGameFile);
		DisplayMessage(buf);
		return False;
	}

	lastGameMode = gameMode;
	gameMode = PlayFromGameFile;
	InitPosition();
	DisplayClocks(StopTimers);
	SendToProgram(appData.initString, toFirstProgFP);
	SendToProgram("force\n", toFirstProgFP);

	/*
	 * skip header information in game record file
	 */
	fgets(buf, MSG_SIZ, gameFileFP);
	fgets(buf, MSG_SIZ, gameFileFP);
	fgets(buf, MSG_SIZ, gameFileFP);

	ReadGameFile();

	return True;
}

void
MachinePlaysBlackProc()
{
	if ((gameMode == EndOfGame) || (gameMode == PlayFromGameFile)
		|| (matchMode != MatchFalse) || ((currentMove % 2) == 0))
		return;

	lastGameMode = gameMode = MachinePlaysBlack;
	SendToProgram("black\n", toFirstProgFP);
}

void
ForwardProc()
{
	char buf[MSG_SIZ];
	int i, j;

	if ((gameMode == EndOfGame) || (gameMode == PlayFromGameFile)
			|| (matchMode != MatchFalse) || (currentMove >= forwardMostMove)
			|| ((currentMove == 0) && (forwardMostMove == 0)))
		return;

	if (forwardForce == False) {
		forwardForce = True;
		SendToProgram("force\n", toFirstProgFP);
	}

	for (i = 0; i < BOARD_SIZE; i++)
		for (j = 0; j < BOARD_SIZE; j++)
			if (boards[currentMove + 1][i][j] != boards[currentMove][i][j])
				DrawSquare(i, j, (int) boards[currentMove + 1][i][j]);

	strcpy(buf, moveList[currentMove++]);
	strncat(buf, "\n", 1);
	SendToProgram(buf, toFirstProgFP);
}

void
ResetProc()
{
	twoProgramState = flipView = forwardForce = False;
	matchMode = MatchFalse;
	firstMove = True;

	ShutdownChessPrograms("");
	lastGameMode = gameMode = BeginningOfGame;
	InitPosition();
	InitChessProgram(appData.firstHost, appData.firstChessProgram,
		&firstProgramPID, &toFirstProgFP, &fromFirstProgFP, &firstProgramXID);
	DisplayClocks(ResetTimers);
}

int
SetupPositionFromFileProc()
{
	char *p, line[MSG_SIZ], buf[MSG_SIZ];
	Board initial_position;
	FILE *position_file_fp;
	int i, j;

	if (gameMode != BeginningOfGame)
		return False;

	if ((position_file_fp = fopen(appData.readPositionFile, "r")) == NULL) {
		sprintf(buf, "Can't open %s", appData.readPositionFile);
		ShutdownChessPrograms(buf);
		lastGameMode = gameMode = BeginningOfGame;
		matchMode = MatchFalse;
		InitPosition();
		return False;
	}

	lastGameMode = gameMode = SetupPosition;

	/*
	 * skip header information in position file
	 */
	fgets(line, MSG_SIZ, position_file_fp);
	fgets(line, MSG_SIZ, position_file_fp);
	fgets(line, MSG_SIZ, position_file_fp);

	for (i = BOARD_SIZE - 1; i >= 0; i--) {
		fgets(line, MSG_SIZ, position_file_fp);
		for (p = line, j = 0; j < BOARD_SIZE; p++) {
			if (*p == ' ')
				continue;
			initial_position[i][j++] = CharToPiece(*p);
		}
	}
	fclose(position_file_fp);

	currentMove = forwardMostMove = 0;
	CopyBoard(boards[0], initial_position);
	SendCurrentBoard(toFirstProgFP);
	DrawPosition(boardWidget, (XExposeEvent *) NULL);

	return True;
}

void
MachinePlaysWhiteProc()
{
	if ((gameMode == EndOfGame) || (gameMode == PlayFromGameFile)
		|| (matchMode != MatchFalse) || ((currentMove % 2) == 1))
		return;

	lastGameMode = gameMode = MachinePlaysWhite;
	SendToProgram("white\n", toFirstProgFP);
}

void
BackwardProc()
{
	int i, j;

	if ((gameMode == EndOfGame) || (gameMode == PlayFromGameFile)
		|| (currentMove <= 0) || (matchMode != MatchFalse))
		return;

	SendToProgram("undo\n", toFirstProgFP);
	currentMove--;

	for (i = 0; i < BOARD_SIZE; i++)
		for (j = 0; j < BOARD_SIZE; j++)
			if (boards[currentMove][i][j] != boards[currentMove + 1][i][j])
				DrawSquare(i, j, (int) boards[currentMove][i][j]);
}

void
FlipProc()
{
	flipView = !flipView;
	DrawPosition(boardWidget, (XExposeEvent *) NULL);
}

void
SaveGameProc()
{
	char buf[MSG_SIZ], white_move[MSG_SIZ], black_move[MSG_SIZ];
	int i = 0, len, move = 0;
	time_t tm;

	if ((gameFileFP = fopen(appData.saveGameFile, "w")) == NULL) {
		sprintf(buf, "Can't open %s", appData.saveGameFile);
		DisplayMessage(buf);
		return;
	}

	tm = time((time_t *) NULL);
	gethostname(buf, MSG_SIZ);

	fprintf(gameFileFP, "xboard game file -- %s", ctime(&tm));
	switch (lastGameMode) {
	case MachinePlaysWhite:
		fprintf(gameFileFP, "\t%s@%s vs. %s@%s\n", appData.firstChessProgram,
			appData.firstHost, getpwuid(getuid())->pw_name, buf);
		break;
	case MachinePlaysBlack:
		fprintf(gameFileFP, "\t%s@%s vs. %s@%s\n", getpwuid(getuid())->pw_name,
			buf, appData.firstChessProgram, appData.firstHost);
		break;
	case TwoMachinesPlay:
		fprintf(gameFileFP, "\t%s@%s vs. %s@%s\n",
			appData.secondChessProgram, appData.secondHost,
			appData.firstChessProgram, appData.firstHost);
		break;
	default:
		fprintf(gameFileFP, "\n");
		break;
	}
	fprintf(gameFileFP, "\talgebraic\n");

	for (;;) {
		if ((len = strlen(moveList[i])) == 0)
			break;
		/*
		 * get rid of '\n' added to send the move to GNU Chess
		 */
		strcpy(white_move, moveList[i++]);
		if (white_move[len - 1] == '\n')
			white_move[len - 1] = NULL;
		fprintf(gameFileFP, "%d. %s ", ++move, white_move);

		if ((len = strlen(moveList[i])) == 0) {
			fprintf(gameFileFP, "\n");
			break;
		}
		strcpy(black_move, moveList[i++]);
		if (black_move[len - 1] == '\n')
			black_move[len - 1] = NULL;
		fprintf(gameFileFP, "%s\n", black_move);
	}
	fclose(gameFileFP);
	gameFileFP = NULL;
}

void
SwitchProc()
{
	switch (gameMode) {
	default:
		return;
	case MachinePlaysWhite:
		lastGameMode = gameMode = MachinePlaysBlack;
		break;
	case MachinePlaysBlack:
		if (currentMove == 0) {
			MachinePlaysWhiteProc();
			return;
		}
		lastGameMode = gameMode = MachinePlaysWhite;
		break;
	}

	SendToProgram("switch\n", toFirstProgFP);
}

void
ForceProc()
{
	if ((gameMode == EndOfGame) || (gameMode == PlayFromGameFile))
		return;

	switch (gameMode) {
	case MachinePlaysWhite:
		if ((currentMove % 2) == 0) {
			DisplayMessage("Wait until your move");
			return;
		}
		break;
	case MachinePlaysBlack:
		if ((currentMove % 2) == 1) {
			DisplayMessage("Wait until your move");
			return;
		}
	case BeginningOfGame:
		break;
	default:
		return;
	}

	lastGameMode = gameMode = ForceMoves;
	DisplayClocks(StopTimers);
	SendToProgram("force\n", toFirstProgFP);
}

void
HintProc()
{
	switch (gameMode) {
	case MachinePlaysWhite:
	case MachinePlaysBlack:
	case BeginningOfGame:
		SendToProgram("hint\n", toFirstProgFP);
	default:
		return;
	}
}

void
SavePositionProc()
{
	char buf[MSG_SIZ], host_name[MSG_SIZ];
	FILE *position_file_fp;
	time_t tm;
	int i, j;

	if ((position_file_fp = fopen(appData.savePositionFile, "w")) == NULL) {
		sprintf(buf, "Can't open %s", appData.savePositionFile);
		DisplayMessage(buf);
		return;
	}

	tm = time((time_t *) NULL);
	gethostname(host_name, MSG_SIZ);

	fprintf(position_file_fp, "xboard position file -- %s", ctime(&tm));
	switch (gameMode) {
	case MachinePlaysWhite:
		fprintf(position_file_fp, "\t%s@%s vs. %s@%s\n",
			appData.firstChessProgram, appData.firstHost,
			getpwuid(getuid())->pw_name, host_name);
		break;
	case MachinePlaysBlack:
		fprintf(position_file_fp, "\t%s@%s vs. %s@%s\n",
			getpwuid(getuid())->pw_name, host_name,
			appData.firstChessProgram, appData.firstHost);
		break;
	case TwoMachinesPlay:
		fprintf(position_file_fp, "\t%s@%s vs. %s@%s\n",
			appData.secondChessProgram, appData.secondHost,
			appData.firstChessProgram, appData.firstHost);
		break;
	default:
		fprintf(position_file_fp, "\n");
		break;
	}
	fprintf(position_file_fp, "\n");

	for (i = BOARD_SIZE - 1; i >= 0; i--) {
		for (j = 0; j < BOARD_SIZE; j++) {
			fprintf(position_file_fp, "%c",
				pieceToChar[boards[currentMove][i][j]]);
			fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', position_file_fp);
		}
	}

	fclose(position_file_fp);
}

void
TwoMachinesPlayProc()
{
	int i;

	if ((gameMode == EndOfGame) || (twoProgramState == True))
		return;

	if (currentMove % 2) {
		DisplayMessage("White must be on move");
		return;
	}

	twoProgramState = True;

	if (matchMode == MatchFalse) {
		switch (gameMode) {
		case MachinePlaysWhite:
		case MachinePlaysBlack:
		case PauseGame:
		case TwoMachinesPlay:
		case PlayFromGameFile:
			return;
		case ForceMoves:
			matchMode = MatchOpening;
			break;
		case SetupPosition:
			matchMode = MatchPosition;
			break;
		case BeginningOfGame:
		default:
			matchMode = MatchInit;
			break;
		}
	}

	flipView = forwardForce = False;
	firstMove = False;
	DisplayClocks(ResetTimers);
	DisplayClocks(ReStartTimers);

	switch (matchMode) {
	case MatchOpening:
		if (firstProgramXID == NULL) {
			InitChessProgram(appData.firstHost, appData.firstChessProgram,
				&firstProgramPID, &toFirstProgFP, &fromFirstProgFP,
				&firstProgramXID);
			if (!PlayFromGameFileProc()) {
				ShutdownChessPrograms("Bad game file");
				return;
			}
			DrawPosition(boardWidget, (XExposeEvent *) NULL);
		}
		InitChessProgram(appData.secondHost, appData.secondChessProgram,
			&secondProgramPID, &toSecondProgFP, &fromSecondProgFP,
			&secondProgramXID);
		SendToProgram("force\n", toSecondProgFP);
		for (i = 0; i < currentMove; i++)
			SendToProgram(moveList[i], toSecondProgFP);
		lastGameMode = gameMode = TwoMachinesPlay;
		firstMove = True;
		SendToProgram("white\n", toSecondProgFP);
		break;
	case MatchPosition:
		if (firstProgramXID == NULL) {
			InitChessProgram(appData.firstHost, appData.firstChessProgram,
				&firstProgramPID, &toFirstProgFP, &fromFirstProgFP,
				&firstProgramXID);
			if (!SetupPositionFromFileProc())
				return;
		}
		InitChessProgram(appData.secondHost, appData.secondChessProgram,
			&secondProgramPID, &toSecondProgFP, &fromSecondProgFP,
			&secondProgramXID);
		SendCurrentBoard(toSecondProgFP);
		lastGameMode = gameMode = TwoMachinesPlay;
		firstMove = True;
		SendToProgram("white\n", toSecondProgFP);
		break;
	case MatchInit:
		InitPosition();
		if (firstProgramXID == NULL)
			InitChessProgram(appData.firstHost, appData.firstChessProgram,
				&firstProgramPID, &toFirstProgFP, &fromFirstProgFP,
				&firstProgramXID);
		InitChessProgram(appData.secondHost, appData.secondChessProgram,
			&secondProgramPID, &toSecondProgFP, &fromSecondProgFP,
			&secondProgramXID);
		lastGameMode = gameMode = TwoMachinesPlay;
		SendToProgram("white\n", toSecondProgFP);
		break;
	}
}

void
PauseProc()
{
	static int previous_mode = PauseGame;

	switch (gameMode) {
	case ForceMoves:
	case EndOfGame:
		return;
	case PauseGame:
		gameMode = previous_mode;
		previous_mode = PauseGame;
		DisplayClocks(ReStartTimers);
		DisplayMessage("");
		break;
	case PlayFromGameFile:
		if (readGameXID == NULL)
			readGameXID = XtAddTimeOut((int) (1000 * appData.timeDelay),
				ReadGameFile, NULL);
		else {
			XtRemoveTimeOut(readGameXID);
			readGameXID = NULL;
		}
		DisplayMessage("Pausing");
		break;
	default:
		if (currentMove == 0)	/* don't pause if no one has moved */
			return;
		previous_mode = gameMode;
		gameMode = PauseGame;
		DisplayClocks(StopTimers);
		DisplayMessage("Pausing");
		break;
	}
}

void
Iconify()
{
	Arg args[1];

	fromX = fromY = -1;

	XtSetArg(args[0], XtNiconic, True);
	XtSetValues(shellWidget, args, 1);
}

void
SendToProgram(message, fp)
	char *message;
	FILE *fp;
{
	if (appData.debugMode)
		fprintf(stderr, "Sending to %s: %s\n",
			fp == toFirstProgFP ? "first" : "second", message);
	fputs(message, fp);
}

void
ReceiveFromProgram(fp)
	FILE *fp;
{
	char message[MSG_SIZ], *end_str;

	if (fgets(message, MSG_SIZ, fp) == NULL)
		return;

	if ((end_str = (char *) strchr(message, '\r')) != NULL)
		*end_str = '\0';
	if ((end_str = (char *) strchr(message, '\n')) != NULL)
		*end_str = '\0';

	if (appData.debugMode)
		fprintf(stderr, "Received from %s: %s\n",
			fp == fromFirstProgFP ? "first" : "second", message);
	HandleMachineMove(message);
}

void
DisplayMessage(message)
	char *message;
{
	Arg arg;

	XtSetArg(arg, XtNlabel, message);
	XtSetValues(messageWidget, &arg, 1);
}

/*
 * DisplayClocks manages the game clocks.
 *
 * In tournament play, black starts the clock and then white makes a move.
 * DisplayClocks starts black's clock when white makes the first move.
 * Also, DisplayClocks doesn't account for network lag so it could get
 * out of sync with GNU Chess's clock -- but then, referees are always right.
 */
void
DisplayClocks(clock_mode)
	int clock_mode;
{
	switch (clock_mode) {
	case ResetTimers:
		if (timerXID != NULL) {
			XtRemoveTimeOut(timerXID);
			timerXID = NULL;
		}

		whiteTimeRemaining = appData.timeControl * 60;
		DisplayTimerLabel(whiteTimerWidget, "White", whiteTimeRemaining);

		blackTimeRemaining = appData.timeControl * 60;
		DisplayTimerLabel(blackTimerWidget, "Black", blackTimeRemaining);
		break;
	case DisplayTimers:
		if (gameMode == PauseGame)
			return;
		if (appData.clockMode) {
			if (currentMove % 2 == 0) {
				if ((currentMove > 0) || (gameMode == TwoMachinesPlay))
					whiteTimeRemaining--;
				if (whiteTimeRemaining <= 0) {
					whiteTimeRemaining = 0;
					DisplayMessage("White's flag dropped");
				}
			} else {
				blackTimeRemaining--;
				if (blackTimeRemaining <= 0) {
					blackTimeRemaining = 0;
					DisplayMessage("Black's flag dropped");
				}
			}
		}

		DisplayTimerLabel(whiteTimerWidget, "White", whiteTimeRemaining);
		DisplayTimerLabel(blackTimerWidget, "Black", blackTimeRemaining);

		/*
		 * reset clocks when time control is acheived
		 */
		if (appData.clockMode
			&& (((currentMove + 1) % (appData.movesPerSession * 2)) == 0)
			&& (whiteTimeRemaining > 0) && (blackTimeRemaining > 0)) {
			whiteTimeRemaining = appData.timeControl * 60;
			blackTimeRemaining = appData.timeControl * 60;
		}

		if (timerXID != NULL) {
			XtRemoveTimeOut(timerXID);
			timerXID = NULL;
		}
		if (appData.clockMode)
			timerXID = XtAddTimeOut(1000, DisplayClocks, DisplayTimers);
		break;
	case StopTimers:
		if (timerXID == NULL)
			return;
		XtRemoveTimeOut(timerXID);
		timerXID = NULL;
		break;
	case ReStartTimers:
		if (timerXID != NULL)
			return;

		DisplayTimerLabel(whiteTimerWidget, "White", whiteTimeRemaining);
		DisplayTimerLabel(blackTimerWidget, "Black", blackTimeRemaining);

		if (appData.clockMode)
			timerXID = XtAddTimeOut(1000, DisplayClocks, DisplayTimers);
		break;
	}
}

void
DisplayTimerLabel(w, color, timer)
	Widget w;
	char *color;
	time_t timer;
{
	char buf[MSG_SIZ];
	Arg args[3];

	if (appData.clockMode) {
		sprintf(buf, "%s: %s", color, TimeString(timer));
		XtSetArg(args[0], XtNlabel, buf);
	} else
		XtSetArg(args[0], XtNlabel, color);

	if (((color[0] == 'B') && (currentMove % 2 == 0))
		|| ((color[0] == 'W') && (currentMove % 2 == 1))) {
		XtSetArg(args[1], XtNbackground, XBlackPixel(xDisplay, xScreen));
		XtSetArg(args[2], XtNforeground, XWhitePixel(xDisplay, xScreen));
	} else {
		XtSetArg(args[1], XtNbackground, XWhitePixel(xDisplay, xScreen));
		XtSetArg(args[2], XtNforeground, XBlackPixel(xDisplay, xScreen));
	}
	XtSetValues(w, args, 3);
}

char *
TimeString(tm)
	time_t tm;
{
	int second, minute, hour, day;
	static char buf[32];

	if (tm >= (60 * 60 * 24)) {
		day = (int) (tm / (60 * 60 * 24));
		tm -= day * 60 * 60 * 24;
	} else
		day = 0;

	if (tm >= (60 * 60)) {
		hour = (int) (tm / (60 * 60));
		tm -= hour * 60 * 60;
	} else
		hour = 0;

	if (tm >= 60) {
		minute = (int) (tm / 60);
		tm -= minute * 60;
	} else
		minute = 0;

	second = tm % 60;

	if (day > 0)
		sprintf(buf, " %d:%02d:%02d:%02d ", day, hour, minute, second);
	else if (hour > 0)
		sprintf(buf, " %d:%02d:%02d ", hour, minute, second);
	else
		sprintf(buf, " %2d:%02d ", minute, second);

	return buf;
}

void
CatchPipeSignal()
{
	if (gameMode != BeginningOfGame) {
		fprintf(stderr, "Caught pipe signal\n");
		exit(0);
	}
}

void
Usage()
{
	fprintf(stderr, "Usage xboard:\n");
	fprintf(stderr, "\tstandard Xt options\n");
	fprintf(stderr, "\t-wpc or -whitePieceColor color\n");
	fprintf(stderr, "\t-bpc or -blackPieceColor color\n");
	fprintf(stderr, "\t-lsc or -lightSquareColor color\n");
	fprintf(stderr, "\t-dsc or -darkSquareColor color\n");
	fprintf(stderr, "\t-mps or -movesPerSession moves\n");
	fprintf(stderr, "\t-init or -initString string\n");
	fprintf(stderr, "\t-fcp or -firstChessProgram program_name\n");
	fprintf(stderr, "\t-scp or -secondChessProgram program_name\n");
	fprintf(stderr, "\t-fh or -firstHost host_name\n");
	fprintf(stderr, "\t-sh or -secondHost host_name\n");
	fprintf(stderr, "\t-spb or -solidPawnBitmap file_name\n");
	fprintf(stderr, "\t-srb or -solidRookBitmap file_name\n");
	fprintf(stderr, "\t-sbb or -solidBishopBitmap file_name\n");
	fprintf(stderr, "\t-skb or -solidKnightBitmap file_name\n");
	fprintf(stderr, "\t-sqb or -solidQueenBitmap file_name\n");
	fprintf(stderr, "\t-skb or -solidKingBitmap file_name\n");
	fprintf(stderr, "\t-opb or -outlinePawnBitmap file_name\n");
	fprintf(stderr, "\t-orb or -outlineRookBitmap file_name\n");
	fprintf(stderr, "\t-obb or -outlineBishopBitmap file_name\n");
	fprintf(stderr, "\t-okb or -outlineKnightBitmap file_name\n");
	fprintf(stderr, "\t-oqb or -outlineQueenBitmap file_name\n");
	fprintf(stderr, "\t-okb or -outlineKingBitmap file_name\n");
	fprintf(stderr, "\t-rsh or -remoteShell shell_name\n");
	fprintf(stderr, "\t-td or -timeDelay seconds\n");
	fprintf(stderr, "\t-tc or -timeControl minutes\n");
	fprintf(stderr, "\t-sgf or -saveGameFile file_name\n");
	fprintf(stderr, "\t-rgf or -readGameFile file_name\n");
	fprintf(stderr, "\t-spf or -savePositionFile file_name\n");
	fprintf(stderr, "\t-rpf or -readPositionFile file_name\n");
	fprintf(stderr, "\t-mm or -matchMode (False | Init | Position | Opening)\n");
	fprintf(stderr, "\t-mono or -monoMode (True | False)\n");
	fprintf(stderr, "\t-debug or -debugMode (True | False)\n");
	fprintf(stderr, "\t-clock or -clockMode (True | False)\n");
	fprintf(stderr, "\t-big or -bigSizeMode (True | False)\n");
	fprintf(stderr, "\t-st or -searchTime seconds\n");

	exit(0);
}

/*
 * 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, length))
			return string;

	return NULL;
}
