
/*  
    xgammon version 0.95.a, a backgammon program

    Copyright (C) 1994	Lambert Klasen & Detlef Steuer
			klasen@asterix.uni-muenster.de
			steuer@amadeus.statistik.uni-dortmund.de

    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.

    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
    COPYING for more details.
 */


#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <X11/IntrinsicP.h>
#include <X11/Shell.h>
#include <X11/Composite.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>

#include <X11/Xaw/Simple.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Cardinals.h>

#include "xgammon.h"
#include "gammon.h"

/* allow.c */
extern int test_move();
extern int move_is_allowed();

/* desicion.c */
extern struct _Move *find_best_move();
extern float evaluate();
extern int doubler();
extern void set_binom();
extern void set_naufm();

/* diawin.c */
extern void AppendDialogText();

/* drawing.c */
extern void draw_board();
extern void DrawDice();
extern void DeleteDice();
extern void DrawEmptyDice();
extern void DrawDiceValues();
extern void draw_doubler();
extern void put_stone();
extern void draw_stone();
extern void remove_stone();
extern void redraw_all_stones();
extern void prepare_pixmaps();
extern void free_pixmaps();

/* edit.c */
extern void EditPosition();

/* filemenu.c */
extern void File();
extern void CreateSaveDialog(void);

/* popup.c */
extern void PopupInfoShell();
extern void CreateXGammonPopup();
extern void ShowComputerAnswerToDoubling();
extern void AcceptResignPopup();

/* misc.c */
extern void init_game();
extern void switch_turn();
extern void roll_dice();
extern int  end_of_game_test(int color);
extern int  complete_blockade(void);
extern void AppendMoveString();
extern void AddResult(int whom);
extern int  event_to_pin();
extern XPoint pin_to_position(int pin);
extern void maildump();

/* load.l */
extern void load();

/* save.c */
extern void save();
extern void sig_save();

/* rollout.c */
extern void RollOut();
extern void exec_rollout();

/* xgammon.c */
void redraw();
void restart();
void UndoMove();
void Quit();
void MenuSelect();
void UndoMove();
static void sigcatch(int n);
static void setup_gc();
static void set_game_kind();
static void PopupButtonShell();
static void ResizeBoard();
static void CreateBoard();
static void checkcmdline(int argc, char *argv[]);
static void TakeStone();
static void MoveStone();
static void PlaceStone();
static void HandleHumanDoubling();
static void HandleComputerDoubling();
static void GetHumanMoves();
static void ShowCompiMove();
static void CompiLoop();
static void HumanLoop();
void XGammonGameLoop();
void XGammonAppTournamentLoop();

void cp_under();
void cp_back();
extern Pixmap underStone, boardPixmap;
int old_place_x, old_place_y;

XtAppContext	app_con;

Widget toplevel, board;				/* top widget and the board */
Widget stone[3], sw[2];				/* the flying stones, dummy, BLACK, WHITE shell with a stone widget */
extern Widget button_shell, form;		/* popup shell with the buttons ... */
extern Widget t_label, p_label; 		/* game and tournment labels */
extern Widget pop_down, quit;			/* command buttons */
extern Widget file_button, setgame, menu, entry;	/* the menus */
extern Widget popup_shell;			/* shell for messages */
extern Widget text_display, tournament_display;	/* text displays in button_shell */

XFontStruct	*doubler_font, *little_font;
Cursor		gammon_cursor;
XColor		fg_col, bg_col; /* gammon_cursor colors */

extern char	cwd[];		/* current working directory */
extern char	add_text[];	/* to text_display */

unsigned long	delaytime;	/* gammon_resource.delaytime * 100000 (microsec / tsec) */
int		font_width;
int		get_moves = 0, took_one = 0;		/* glabal flags */
int		end_of_tournament = 0, end_of_game = 0;	/* " */
int		initialize = 0;				/* " */
int		break_loop = 0, doubling_done = 1;	/* " */

int		from_pin;
int		done_hit, current_hit[4];	/* for UndoMove() */
extern int	done_yet;			/* " */
extern struct _Move current_move[];		/* " */
struct _Move	*compi_choice;

char * greetings = "Wellcome to xgammon  version 0.96\n   (C) 1994   Lambert Klasen   Detlef Steuer\n    We hope you enjoy it\n\n";

String fallback_resources[] = {
	"*boardColor:		tan1",
	"*darkColor:		tan3",
	"*lightColor:		tan",
	"*barColor:		saddlebrown",
	"*whiteColor:		white",
	"*blackColor:		black",
	"*boardGeometry:	390x312",
	"*board.Translations: #replace		\\n\
		<Key>u:			UndoMove()	\\n\
		Ctrl<Key>c:		Quit()		\\n\
		Ctrl<Key>d:		Quit()		\\n\
		Ctrl<Key>z:		Quit()		\\n\
		<Key>q:			Quit()		\\n\
		Shift<Key>r:		redraw()	\\n\
		Shift<Key>S:		save(game)	\\n\
		<Key>s:			save(position)	\\n\
		Shift<Key>L:		load(game)	\\n\
		<Key>l:			load(position)	\\n\
		<Key>R:			restart()",
	NULL,
};

XtActionsRec gammon_actions[] = {
	{"redraw",		redraw},
	{"UndoMove",		UndoMove},
	{"ResizeBoard",		ResizeBoard},
	{"Quit",		Quit},
	{"save",		save},
	{"load",		load},
	{"restart",		restart},
};

static XtResource gammon_resources[] = {
#define offset(field)   XtOffsetOf(struct _gammon_resource, field)
{"boardGeometry", "Geometry",     XtRString,  sizeof(char *),  offset(board_geometry),XtRString, (caddr_t) &gammon_resource.board_geometry},
{"boardColor",    "Background",   XtRPixel,   sizeof(Pixel),   offset(board_Pixel),   XtRPixel,  (caddr_t) &gammon_resource.board_Pixel},
{"boardColor",    "Background",   XtRString,  sizeof(char *),  offset(board_color),   XtRString, (caddr_t) NULL},
{"lightColor",    "Background",   XtRPixel,   sizeof(Pixel),   offset(light_Pixel),   XtRPixel,  (caddr_t) &gammon_resource.light_Pixel},
{"lightColor",    "Background",   XtRString,  sizeof(char *),  offset(light_color),   XtRString, (caddr_t) NULL},
{"darkColor",     "Background",   XtRPixel,   sizeof(Pixel),   offset(dark_Pixel),    XtRPixel,  (caddr_t) &gammon_resource.dark_Pixel},
{"darkColor",     "Background",   XtRString,  sizeof(char *),  offset(dark_color),    XtRString, (caddr_t) NULL},
{"barColor",      "Foreground",   XtRPixel,   sizeof(Pixel),   offset(bar_Pixel),     XtRPixel,  (caddr_t) &gammon_resource.bar_Pixel},
{"barColor",      "Foreground",   XtRString,  sizeof(char *),  offset(bar_color),     XtRString, (caddr_t) NULL},
{"whiteColor",    "Background",   XtRPixel,   sizeof(Pixel),   offset(white_Pixel),   XtRPixel,  (caddr_t) &gammon_resource.white_Pixel},
{"whiteColor",    "Background",   XtRString,  sizeof(char *),  offset(white_color),   XtRString, (caddr_t) NULL},
{"blackColor",    "Foreground",   XtRPixel,   sizeof(Pixel),   offset(black_Pixel),   XtRPixel,  (caddr_t) &gammon_resource.black_Pixel},
{"blackColor",    "Foreground",   XtRString,  sizeof(char *),  offset(black_color),   XtRString, (caddr_t) NULL},
{"littleFont",    "Font",         XtRString,  sizeof(char *),  offset(little_font),   XtRString, (caddr_t) "-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*"},
{"doublerFont",   "Font",         XtRString,  sizeof(char *),  offset(doubler_font),  XtRString, (caddr_t) "-*-helvetica-medium-r-normal-*-24-*-*-*-*-*-*-*"},
{"humanStones",   "String",       XtRString,  sizeof(char *),  offset(human_stone),   XtRString, (caddr_t) "black"},
{"gamekind",      "Gamekind",     XtRString,  sizeof(char *),  offset(gamekind),      XtRString, (caddr_t) "hvc"},
{"stonesteps",    "Stonesteps",   XtRInt,     sizeof(int),     offset(stone_steps),   XtRString, "5"},
{"delaytime",     "delaytime",    XtRInt,     sizeof(int),     offset(delaytime),     XtRString, "10"},
{"watchmove",     "Watchmove",    XtRBoolean, sizeof(int),     offset(watchmove),     XtRString, "1"},
{"doubling",      "Doubling",     XtRBoolean, sizeof(int),     offset(doubling),      XtRString, "1"},
{"getdice",       "Getdice",      XtRBoolean, sizeof(int),     offset(getdice),       XtRString, "0"},
{"moneygame",     "Moneygame",    XtRBoolean, sizeof(int),     offset(moneygame),     XtRString, "1"},
{"bestof",        "Bestof",       XtRInt,     sizeof(int),     offset(bestof),        XtRString, "0"},
{"winat",         "Winat",        XtRInt,     sizeof(int),     offset(winat),         XtRString, "0"},
{"rollout",       "Rollout",      XtRBoolean, sizeof(int),     offset(rollout),       XtRString, "0"},
{"numrollouts",   "NumRollouts",  XtRInt,     sizeof(int),     offset(num_rollouts),  XtRString, "100"},
{"positionfile",  "PositionFile", XtRString,  sizeof(char *),  offset(position_file), XtRString, "xgammon.save"},

#undef  offset
};

static XrmOptionDescRec options[] = {
{"-boardgeometry", ".boardGeometry", XrmoptionSepArg,  NULL},
{"-boardcolor",    ".boardColor",    XrmoptionSepArg,  NULL},
{"-bc",            ".boardColor",    XrmoptionSepArg,  NULL},
{"-darkcolor",     ".darkColor",     XrmoptionSepArg,  NULL},
{"-dc",            ".darkColor",     XrmoptionSepArg,  NULL},
{"-lightcolor",    ".lightColor",    XrmoptionSepArg,  NULL},
{"-lc",            ".lightColor",    XrmoptionSepArg,  NULL},
{"-barcolor",      ".barColor",      XrmoptionSepArg,  NULL},
{"-b",             ".barColor",      XrmoptionSepArg,  NULL},
{"-whitecolor",    ".whiteColor",    XrmoptionSepArg,  NULL},
{"-blackcolor",    ".blackColor",    XrmoptionSepArg,  NULL},
{"-doublerfont",   ".doublerFont",   XrmoptionSepArg,  NULL},
{"-h",             ".humanStones",   XrmoptionSepArg,  NULL},
{"-g",             ".gamekind",      XrmoptionSepArg,  NULL},
{"-gamekind",      ".gamekind",      XrmoptionSepArg,  NULL},
{"-bestof",        ".bestof",        XrmoptionSepArg,  NULL},
{"-winat",         ".winat",         XrmoptionSepArg,  NULL},
{"-rollout",       ".rollout",       XrmoptionNoArg,   "1"},
{"-nr",            ".numrollouts",   XrmoptionSepArg,  NULL},
{"-f",             ".positionfile",  XrmoptionSepArg,  NULL},
{"-stonesteps",    ".stonesteps",    XrmoptionSepArg,  NULL},
{"-delaytime",     ".delaytime",     XrmoptionSepArg,  NULL},
{"-moneygame",     ".moneygame",     XrmoptionNoArg,   "0"},
{"+moneygame",     ".moneygame",     XrmoptionNoArg,   "1"},
{"-watchmove",     ".watchmove",     XrmoptionNoArg,   "0"},
{"+watchmove",     ".watchmove",     XrmoptionNoArg,   "1"},
{"-getdice",       ".getdice",       XrmoptionNoArg,   "1"},
{"-doubling",      ".doubling",      XrmoptionNoArg,   "0"},
{"+doubling",      ".doubling",      XrmoptionNoArg,   "1"},
};

int main(int argc, char **argv)
{
Arg args[3];

signal(SIGHUP, sigcatch);
signal(SIGINT, sigcatch);
signal(SIGQUIT, sigcatch);
signal(SIGSEGV, sigcatch);
signal(SIGFPE, sigcatch);
signal(SIGPIPE, sigcatch);
signal(SIGTERM, sigcatch);	/* for debugging */

getcwd((char *)cwd, 1024);              /* for the file menu */

toplevel = XtAppInitialize(&app_con, "XGammon", options, XtNumber(options),
			       &argc, argv, fallback_resources, NULL, ZERO);

XtGetApplicationResources( toplevel, (caddr_t) &gammon_resource, gammon_resources, XtNumber(gammon_resources), NULL, (Cardinal) 0);
XtAppAddActions(app_con, gammon_actions, XtNumber(gammon_actions));

dpy  = XtDisplay(toplevel);
scrn = DefaultScreenOfDisplay(dpy);
screen_num   = DefaultScreen(dpy);
screen_depth = DefaultDepth(dpy, screen_num);

checkcmdline(argc, argv);

/* initialize for decision.c */
set_binom();
set_naufm();

/* and for the dice */
srand(time(NULL));

/* and the endgame */
endgame_database = fopen(DATABASE, "rb");
if (!endgame_database) fprintf (stderr, "endgame database not found\nyou should create one\n");

if (gammon_resource.rollout) RollOut();

delaytime = ((unsigned long) gammon_resource.delaytime) * 100000;

CreateBoard();
CreateXGammonPopup();

XtRealizeWidget(toplevel);
PopupButtonShell();

boardWindow = XtWindow(board);

setup_gc();
prepare_pixmaps();

AppendDialogText(tournament_display, greetings);

if      (gammon_resource.moneygame) {
	sprintf (add_text, "money-game:\n");
	tournament.winning_point = 0;
}
if (gammon_resource.bestof) {
	sprintf (add_text, "tournament: best of %d\n", gammon_resource.bestof);
	tournament.winning_point = gammon_resource.bestof;
	gammon_resource.moneygame = 0;
}
if (gammon_resource.winat)  {
	sprintf (add_text, "tournament: win at %d points\n", gammon_resource.winat);
	tournament.winning_point = gammon_resource.winat;
	gammon_resource.moneygame = 0;
	gammon_resource.bestof = 0;
}
AppendDialogText(tournament_display, add_text);
AppendDialogText(tournament_display, "black points:  0, white points:  0\n");

if      (!strcmp(gammon_resource.gamekind, "hvc")) {
	if      (!strcmp(gammon_resource.human_stone,"black")) {
		Player[0].type = HUMAN;
		Player[1].type = COMPUTER;
		}
	else if (!strcmp(gammon_resource.human_stone,"white")) {
		Player[0].type = COMPUTER;
		Player[1].type = HUMAN;
	}
}
else if (!strcmp(gammon_resource.gamekind, "cvc")) {
	Player[0].type = COMPUTER;
	Player[1].type = COMPUTER;
	initialize = COMPUTER_TOURNAMENT;
}
else if (!strcmp(gammon_resource.gamekind, "hvh")) {
	Player[0].type = HUMAN;
	Player[1].type = HUMAN;
}

Player[0].color = BLACK;
Player[1].color = WHITE;

/* prepare the cursor, looks like the stones you move */
fg_col.pixel = gammon_resource.black_Pixel;
bg_col.pixel = gammon_resource.white_Pixel;

XQueryColor(dpy, DefaultColormapOfScreen(scrn), &fg_col);
XQueryColor(dpy, DefaultColormapOfScreen(scrn), &bg_col);

XtSetArg(args[0], XtNcursor, &gammon_cursor);
XtGetValues (board, args, 1);

if      (Player[0].type == HUMAN) XRecolorCursor(dpy, gammon_cursor, &fg_col, &bg_col);
else if (Player[1].type == HUMAN) XRecolorCursor(dpy, gammon_cursor, &bg_col, &fg_col);
else				  XRecolorCursor(dpy, gammon_cursor, &fg_col, &bg_col);

while (1) {
	end_of_tournament = 0;
	XGammonAppTournamentLoop();
	restart();
}

return 0;
}

/*	XGammonAppTournamentLoop:
	the main event loop for xgammon 
 */

void XGammonAppTournamentLoop(void)
{
	while (!end_of_tournament) {
		init_game();
		if (initialize != EDITED_POSITION) {
		if (tournament.game_number == 1) {
			if ((Player[0].beginner_of_game && Player[0].type == COMPUTER) ||
			    (Player[1].beginner_of_game && Player[1].type == COMPUTER)) {
				/* make startup look a little nicer */
				PopupInfoShell("click once on the board to start game\n");
				WaitForEvent(ButtonRelease);
				XtPopdown(popup_shell);
			}
		}
		}
		XGammonGameLoop();
		AddResult(turn);

		if (tournament.winning_point) {
		if (tournament.points[BLACK] >= tournament.winning_point ||
		    tournament.points[WHITE] >= tournament.winning_point) {
			end_of_tournament = 1;
			sleep(7);
		}
		}

		/* reset computer finish */
		if (initialize == NORMAL_GAME && Player[0].type == COMPUTER && Player[1].type == COMPUTER)
			Player[0].type = HUMAN;
	}
}

/*	XGammonGameLoop:
	event loop for a single game 
 */

void XGammonGameLoop(void)
{
	end_of_game = 0;

	if (initialize != EDITED_POSITION) {
	if (Player[0].beginner_of_game) {
		if (Player[0].type == HUMAN) {
			DrawDice(turn);
			test_move();
			GetHumanMoves();
		}
		else    {	/* COMPUTER */
			test_move();
			compi_choice = (struct _Move *) find_best_move();
			AppendMoveString(compi_choice);
			ShowCompiMove();
		}
		switch_turn();
		if (Player[1].type == HUMAN) HumanLoop();
		else 			     CompiLoop();
	}
	else {
		if (Player[1].type == HUMAN) {
			DrawDice(turn);
			test_move();
			GetHumanMoves();
		}
		else  {
			test_move();
			compi_choice = (struct _Move *) find_best_move();
			AppendMoveString(compi_choice);
			ShowCompiMove();
		}
	}
	}	/* !EDITED_POSITION */
	else {	/* EDITED_POSITION */
		DrawDice(turn);
		draw_doubler(doubler_value, last_doubler);
		initialize = NORMAL_GAME;
		if (turn == BLACK) {
			if (Player[0].type == HUMAN) {
				if (test_move()) GetHumanMoves();
				else usleep (delaytime);
			}
			else {
				if (test_move()) {
					compi_choice = (struct _Move *) find_best_move();
					AppendMoveString(compi_choice);
					ShowCompiMove();
					}
				else	{
					AppendDialogText(text_display, "no move\n");
					XSync(dpy, 0);
					usleep (delaytime);
				}
			}
			switch_turn();
			if (Player[1].type == HUMAN) HumanLoop();
			else			     CompiLoop();
		}
		else {
			if (Player[1].type == HUMAN) {
				if (test_move()) GetHumanMoves();
				else usleep (delaytime);
			}
			else {
				if (test_move()) {
					compi_choice = (struct _Move *) find_best_move();
					AppendMoveString(compi_choice);
					ShowCompiMove();
					}
				else	{
					AppendDialogText(text_display, "no move\n");
					XSync(dpy, 0);
					usleep (delaytime);
				}
			}
		}
	}

	while (!end_of_game) {
		switch_turn();
		if (Player[0].type == HUMAN) HumanLoop();
		else			     CompiLoop();
		
		if (!end_of_game) {
			switch_turn();
			if (Player[1].type == HUMAN) HumanLoop();
			else			     CompiLoop();
		}
	}
}

/*	HumanLoop:
	event loop for a human player 
 */

void HumanLoop(void) 
{
	if (Pin[BAR].count && complete_blockade()) return;
	if (gammon_resource.doubling) {
		doubling_done = 0;
		if (last_doubler != turn) {
			DeleteDice();
			DrawEmptyDice(turn);
			HandleHumanDoubling();
			if (end_of_game) return;	/* other resigned */
		}
		doubling_done = 1;
	}

	roll_dice();
	DrawDice(turn);

	if (test_move()) GetHumanMoves();
	else {
		AppendDialogText(text_display, "no move\n");
		XSync(dpy, 0);
		usleep (delaytime);
	}
}

void HandleHumanDoubling(void)
{
	int p = 0;

	if (last_doubler == turn || doubler_value == 64) return;

	while (1) {
		XEvent event;
		XtAppNextEvent(app_con, &event);
		if (event.type == ButtonRelease) {	/* not Press */
			if (event.xbutton.window == boardWindow) {
				p = event_to_pin(event.xbutton.x, event.xbutton.y);
			}
			XtDispatchEvent(&event);
			break;
		}
		else if (event.type == KeyPress) {
			KeySym k;
			Modifiers m;
			k = XtGetActionKeysym(&event, &m);
			if (k == 'd') p = DOUBLER;
			XtDispatchEvent(&event);
			break;
		}
		XtDispatchEvent(&event);
	}

	if (p == DOUBLER) {

		last_doubler = turn;

		/* check opponent */
		if (Player[0].type == COMPUTER || Player[1].type == COMPUTER) {
			if (doubler(ANSWER)) {
				doubler_value *= 2;
				draw_doubler(doubler_value, last_doubler);
				if (turn == BLACK)
					sprintf (add_text,"black doubled to %d\n", doubler_value);
				else
					sprintf (add_text,"white doubled to %d\n", doubler_value);
				AppendDialogText(text_display, add_text);
				ShowComputerAnswerToDoubling(1);
			}
			else {	/* comuter resigned */
				ShowComputerAnswerToDoubling(0);
				if (turn == BLACK) AppendDialogText(text_display, "doubling offered: white resigned\n");
				else		   AppendDialogText(text_display, "doubling offered: black resigned\n");
				end_of_game = 1;
			}
			WaitForEvent(ButtonPress);
			XtPopdown(popup_shell);
		}
		else {
			AcceptResignPopup();
			if (!end_of_game) {
				doubler_value *= 2;
				draw_doubler(doubler_value, last_doubler);
			}
		}
	}
}

void GetHumanMoves(void)
{
	get_moves = 1;
	while (roll[0]) {
		XEvent event;
		XtAppNextEvent(app_con, &event);
		XtDispatchEvent(&event);
		if (break_loop) {	/* computer shall finish */
			break_loop = 0;
			break;
		}
	}
	get_moves = 0;
}

/*	CompiLoop:
	event loop for a computer player 
 */

void CompiLoop(void)
{
	if (Pin[BAR].count && complete_blockade()) return;
	if (gammon_resource.doubling) {
		HandleComputerDoubling();
		if (end_of_game) return;	/* other resigned */
	}

	roll_dice();
	DrawDice(turn);
	XSync(dpy, 0);
	if (!gammon_resource.watchmove) usleep(delaytime);
	if (test_move()) {
		compi_choice = (struct _Move *) find_best_move();
		AppendMoveString(compi_choice);
		ShowCompiMove();
		}
	else	{
		AppendDialogText(text_display, "no move\n");
		usleep (delaytime);
	}
}

void HandleComputerDoubling(void)
{
	if (last_doubler == turn || doubler_value == 64) return;

	if (! doubler (OFFER)) return;

	last_doubler = turn;

	/* check opponent */
	if (Player[0].type == HUMAN || Player[1].type == HUMAN) {
		AcceptResignPopup();
		if (!end_of_game) {
			doubler_value *= 2;
			draw_doubler(doubler_value, last_doubler);
			if (turn == BLACK)
				sprintf (add_text,"black doubled to %d\n", doubler_value);
			else
				sprintf (add_text,"white doubled to %d\n", doubler_value);
			AppendDialogText(text_display, add_text);
		}
		else {
			if (turn == BLACK) AppendDialogText(text_display, "doubling offered: white resigned\n");
			else		   AppendDialogText(text_display, "doubling offered: black resigned\n");
		}
	}
	else {	/* opponent is computer */
		if (! doubler (ANSWER)) {	/* other resigned */
			end_of_game = 1;
			if (turn == BLACK) AppendDialogText(text_display, "doubling offered: white resigned\n");
			else		   AppendDialogText(text_display, "doubling offered: black resigned\n");
		}
		else {
			doubler_value *= 2;
			draw_doubler(doubler_value, last_doubler);
		}
	}
}

void ShowCompiMove(void)
{
	XPoint   move_start, move_end;
	int	 x, y, dx, dy, steps;
	int      i, j;

	for (i=0; i<to_move; i++) {
	remove_stone((compi_choice+i)->from);

	if (gammon_resource.watchmove) {
		move_start = pin_to_position((compi_choice+i)->from);
		if ((compi_choice+i)->to != end_pin)
			move_end   = pin_to_position((compi_choice+i)->to);
		else
			move_end   = pin_to_position(FINISHED);

		x = move_start.x;
		y = move_start.y;

		if (abs((move_start.x - move_end.x)) > abs((move_start.y - move_end.y)))
			steps = abs(move_start.x - move_end.x)/gammon_resource.stone_steps;
		else    steps = abs(move_start.y - move_end.y)/gammon_resource.stone_steps;

		/*cp_under(x,y);*/
		draw_stone(x,y);
		old_place_x = x;
		old_place_y = y;

		dx = (move_start.x - move_end.x) * -1 / steps;
		dy = (move_start.y - move_end.y) * -1 / steps;

		for (j=0; j<steps; j++) {
			x += dx;
			y += dy;
			cp_back(old_place_x, old_place_y);
		/*cp_under(x,y);*/
			draw_stone(x, y, turn);
			XSync(dpy, 0);
			old_place_x = x;
			old_place_y = y;
		}
		cp_back(old_place_x, old_place_y);
	}			/* watchmove */

	if ((compi_choice+i)->to == end_pin) {	/* endpin check must be first cause of removing other from endpin, i. e. other bar */
		put_stone(turn, FINISHED);
		if ((end_of_game = end_of_game_test(turn)))
			return;
		}
	else if (Pin[(compi_choice+i)->to].color == other) {
		remove_stone((compi_choice+i)->to); 
		put_stone(other, OTHER_BAR);
		put_stone(turn, (compi_choice+i)->to);
	}
	else put_stone(turn, (compi_choice+i)->to);

	XSync (dpy, 0);
	if (!gammon_resource.watchmove) usleep (delaytime);

	/* look for events happened while moveing */
	while (XtAppPending(app_con)) {
		XEvent event;
		XtAppNextEvent(app_con, &event);
		XtDispatchEvent(&event);
	}
	}
}

void cp_under(int x, int y)
{
	XCopyArea(dpy, boardWindow, underStone, boardGC,
	        x, y, stone_width, stone_width, 0, 0);
}

void cp_back(int x, int y)
{
	/*XCopyArea(dpy, underStone, boardWindow, boardGC,
	        0, 0, stone_width, stone_width, x, y);*/
	XCopyArea(dpy, boardPixmap, boardWindow, boardGC,
	        x, y, stone_width, stone_width, x, y);
}

static void CreateBoard(void)
{
	WidgetClass board_class;
	Arg args[2];
	static XtActionsRec board_actions[] = {
		{"redraw",		redraw},
		{"ResizeBoard",		ResizeBoard},
		{"TakeStone",		TakeStone},
		{"MoveStone",		MoveStone},
		{"PlaceStone",		PlaceStone},
		{NULL, NULL}
	};

	static String translations = "#override\n\
		<Expose>:    		redraw()     \n\
		<ConfigureNotify>:      ResizeBoard()   \n\
		Button1<Btn1Motion>:    MoveStone()     \n\
		<ButtonRelease>:        PlaceStone()    \n\
		<ButtonPress>:          TakeStone()     \n\
	";


/* ok. I got a board with a width of about 60 cm, 22.5 cm each board part,
   and a height of about 52 cm, so height is width * 52 / 60
   this board was my model

   that fast I couldn't get separated board geometry and normal Xt -geometry
   option, so there is this strange and ugly boardgeometry,
   the reason is, that there is no toplevel width and height before XtRealize.
   further XParseGeometry doesn't parse very low values like 10x10 or 0x0
   (even so the board wouldn't look like something, I'd have a hint) I put 
   in this 390x312 dummyvalue for boardsize
 */

{
	int dummy_x, dummy_y; 		/* of no interest, at least yet */
	(void) XParseGeometry(gammon_resource.board_geometry,
			      &dummy_x, &dummy_y, &width, &height);
	if (width < 150 || height < 120) {
		width = 150; height = 120;
		fprintf(stderr,"boardsize should at least be 150x120 pixel\ngeometry values changed\n");
	}
}

	if (width  > DisplayWidth( dpy, screen_num) - 50 || height > DisplayHeight(dpy, screen_num) - 50) {
		width  = DisplayWidth( dpy, screen_num) - 50;
		height = DisplayHeight(dpy, screen_num) - 50;
	}

	stone_width = stone_height = width/15;

	if (height < stone_width * 12) stone_width = height/12;		/* some kinda screens there are */

	width  = stone_width * 15;
	height = stone_width * 12;

	XtSetArg(args[0], XtNwidth,  width);
	XtSetArg(args[1], XtNheight, height);

	board = XtCreateManagedWidget("board", simpleWidgetClass, toplevel, args, 2);
	XtOverrideTranslations(board, XtParseTranslationTable(translations));
	XtAppAddActions(app_con, board_actions, XtNumber(board_actions));

	board_class = (WidgetClass) XtClass(board);
	board_class->core_class.compress_exposure = XtExposeCompressMaximal;
	board_class->core_class.compress_motion = True;
}

void sigcatch(int n)
{
	sig_save ();
}

/* Quit is both, Callback and Action routine. this is not too proper,
   but works, and I'm too lazy to invent an extra Callback */
void Quit (Widget w, XEvent *e, String *vector, Cardinal *count)
{
	XtDestroyApplicationContext(XtWidgetToApplicationContext(w));
	fclose (endgame_database);
	exit(0);
}

static void checkcmdline(int argc, char *argv[])
{
	int option;

	for(option=1; option<argc; option++) {
		if (strcmp(argv[option], "-?") == 0 ||
		    strcmp(argv[option], "-help") == 0) {
puts ("xgammon  [-boardgeometry (string)]
\t [-boardcolor (color)] [-bc (color)]
\t [-darkcolor  (color)] [-dc (color)]
\t [-lightcolor (color)] [-lc (color)]
\t [-barcolor   (color)] [-b  (color)]
\t [-whitecolor (color)] \t\tthe stone color for \"white\"
\t [-blackcolor (color)] \t\tthe stone color for \"black\"
\t [-doublerfont (font)] \t\tsets the (big) font for the doubler dice
\t [-doubling +doubling] \t\tsets if you wish the computer to double
\t [-h (black|white)] \t\tset the color for the human player
\t\t\t\t\t(implies a human versus computer game)
\t [-g -gamekind (hvc|cvc|hvh)] \tsets the gamekind to eiher
\t\t\t\t\thuman vs. human computer vs. computer
\t\t\t\t\tor human vs. computer (default)
\t [-winat (points)] \t\tplay a tournament up to \"points\" points
\t [-bestof (games)] \t\tplay a tournament of \"number\" games
\t [-watchmove +wachmove] \tif you wish to see the flying stones
\t [-stonesteps (number)] \tsets the stepsize the stones fly
\t\t\t\t\tin pixel (implies +watchmove)
\t [-delaytime (tsec.)] \tsets the time the cumputer waits
\t\t\t\t\tafter each turn (implies -watchmove)
\t [-?] [-help]\t\t\tprint this string");
		exit (0);
		}
	}
        if (!gammon_resource.stone_steps) {
                gammon_resource.watchmove = 0;
                fprintf(stderr,"stonesteps must be > 0 with watchmove or floating point exeption will occur\n");
        }
}

void setup_gc()
{
	XGCValues values;
	XtGCMask  valuemask;

	valuemask = GCForeground|GCBackground|GCFunction;

	values.function   = GXcopy;
	values.foreground = gammon_resource.black_Pixel;
	values.background = gammon_resource.board_Pixel;
	gc	   = XtGetGC(board, valuemask, &values);

	values.foreground = gammon_resource.board_Pixel;
	values.background = gammon_resource.board_Pixel;
	boardGC    = XtGetGC(board, valuemask, &values);

	values.foreground = gammon_resource.black_Pixel;	/* black stone */
	values.background = gammon_resource.white_Pixel;
	stoneGC[0] = XtGetGC(board, valuemask, &values);

	values.foreground = gammon_resource.white_Pixel;
	values.background = gammon_resource.black_Pixel;
	stoneGC[1] = XtGetGC(board, valuemask, &values);

	valuemask ^= GCLineWidth|GCCapStyle|GCFont;

	values.foreground = gammon_resource.black_Pixel;
	values.background = gammon_resource.white_Pixel;
	values.line_width = 3;
	values.cap_style  = CapRound;

	/* the fonts actually used */
	if ((doubler_font = XLoadQueryFont(dpy, gammon_resource.doubler_font)) == NULL) {
		fprintf(stderr, "couldn't load doubler dice font, using default, sorry\n");
	}
	else values.font = doubler_font->fid;

	diceGC     = XtGetGC(board, valuemask, &values);

	little_font = XLoadQueryFont(dpy, gammon_resource.little_font);
}

void redraw(Widget w, XEvent *e, String *vector, Cardinal *count)
{
	draw_board();
	redraw_all_stones();
	DrawEmptyDice(turn);
	if (doubling_done) DrawDiceValues(turn);
	draw_doubler(doubler_value, last_doubler);
}

static void ResizeBoard(Widget w, XConfigureEvent *e, String *vector, Cardinal *count)
{
	Arg arg[2];
	Dimension new_stone_width, new_stone_height;
	Dimension new_width, new_height;
	XClearArea(dpy, boardWindow, 0, 0, width, height, 0);

	new_stone_width = new_stone_height = e->width/15;
	new_width       = new_stone_width * 15;

	if (e->height < new_stone_width * 12) {
		new_stone_width = e->height/12;
		new_height = new_stone_width * 12;
		new_width  = new_stone_width * 15;
	}
	else new_height = new_stone_width * 12;

	width  += (new_width-width);
	height += (new_height-height);
	stone_width = new_stone_width;

	XtSetArg(arg[0], XtNwidth, width);
	XtSetArg(arg[1], XtNheight, height);
	XtSetValues(board, arg, 2);

	free_pixmaps();
	prepare_pixmaps();

	redraw(0L, 0L, (char**) 0L, 0);
}

void MenuSelect(Widget w, XtPointer junk, XtPointer garbage)
{
char *v[] = {"p", NULL};

if      (strncmp(XtName(w),"save position",13) == 0 ) save(w, 0L, v, 0);
else if (strncmp(XtName(w),"load position",13) == 0 ) load(w, 0L, v, 0);
else if (strncmp(XtName(w),"load game",9) == 0 ) {
	v[0] = "g";
	load(w, 0L, v, 0);
}
else if (strncmp(XtName(w),"save game",9) == 0 ) { 
	v[0] = "g";
	save(w, 0L, v, 0);
}
else if (strcmp(XtName(w),"save as ..") == 0) CreateSaveDialog();
else if (strcmp(XtName(w),"load ..")    == 0) File();
else if (strncmp(XtName(w),"restart",7) == 0) restart();
else if (strncmp(XtName(w),"undo move",9) == 0) UndoMove(0L, 0L, (char **) 0L, 0);
else if (strcmp(XtName(w),"compi vs. compi") == 0) set_game_kind(COMPI_VS_COMPI);
else if (strcmp(XtName(w),"human vs. compi") == 0) set_game_kind(HUMAN_VS_COMPI);
else if (strcmp(XtName(w),"human vs. human") == 0) set_game_kind(HUMAN_VS_HUMAN);
else if (strcmp(XtName(w),"computer finish") == 0) {
	set_game_kind(COMPI_VS_COMPI);
	if (done_yet) {
		PopupInfoShell("Please finish your move,\nI don't want to complete it\nI'll continue next turn");
	}
	else {
		break_loop = FINISH_GAME;
		if (!doubling_done) {
			roll_dice();
			DrawDice(turn);
			if (!test_move()) {
				AppendDialogText(text_display, "no move\n");
				return;
			}
		}
		compi_choice = (struct _Move *) find_best_move();
		AppendMoveString(compi_choice);
		ShowCompiMove();
	}
}
else if (strcmp(XtName(w),"rollout") 	   == 0) exec_rollout();
else if (strcmp(XtName(w),"edit position") == 0) EditPosition();
else if (strcmp(XtName(w),"mail dump")     == 0) maildump();
}

void TakeStone(Widget w, XButtonEvent *e)
{
	int p;

	if (!get_moves) return;

	p = event_to_pin(e->x, e->y);

	if (Pin[BAR].count && p != BAR) {
		from_pin = 0;
		return;
	}

	if (Pin[p].count && Pin[p].color == turn) {
		from_pin = p;
		remove_stone(p);
		old_place_x = e->x - stone_width/2;
		old_place_y = e->y - stone_width/2;
		/*cp_under(old_place_x, old_place_y);*/
		draw_stone(old_place_x, old_place_y, turn);
		took_one = 1;
		}
	else {
		from_pin = 0;
	}

}

void MoveStone(Widget w, XButtonEvent *e)
{
	int x, y;

	if (!took_one) return;

	x = e->x - stone_width/2;
	y = e->y - stone_width/2;

	if (x<0) x = 0;
	else if (x > width - stone_width)  x = width  - stone_width;

	if (y<0) y = 0;
	else if (y > height - stone_width) y = height - stone_width;

	cp_back(old_place_x, old_place_y);
	/*cp_under(x,y);*/
	draw_stone(x, y, turn);
	old_place_x = x;
	old_place_y = y;
}

void PlaceStone(Widget w, XButtonEvent *e)
{
	int p;

	if (!get_moves) return;

	if (!took_one) return;
	took_one = 0;

	cp_back(old_place_x, old_place_y);

	p = event_to_pin(e->x, e->y);

	if (!move_is_allowed(from_pin, p)) {
		put_stone(turn, from_pin);
		return;
	}

	if (p == end_pin) {		/* check this first, cause possible removing other from endpin (other bar) */
		put_stone(turn, FINISHED);
		if ((end_of_game = end_of_game_test(turn))) return;
	}
	else if (Pin[p].color == other) {
		remove_stone(p);
		current_hit [done_hit] = p;
		done_hit++;
		put_stone(other, OTHER_BAR);
		put_stone(turn, p);
	}
	else put_stone(turn, p);
}

void UndoMove(Widget w, XEvent *e, String *vector, Cardinal *count)
{
	int i;

	for (i=0; i<done_yet; i++) {
		remove_stone (current_move[i].to);
		put_stone (turn, current_move[i].from);
	}


	for (i=0; i<done_hit; i++) {
		put_stone (other, current_hit[i]);
		remove_stone (OTHER_BAR);
	}

	done_yet = 0;
	done_hit = 0;
}

void PopupButtonShell(void)
{
	Position x, y;
	Dimension w;
	Arg args[2];

	XtSetArg(args[0], XtNwidth,  &w);
	XtGetValues(toplevel, args, 1);

	XtSetArg(args[0], XtNx, &x);
	XtSetArg(args[1], XtNy, &y);
	XtGetValues(toplevel, args, 2);

	x += (Position) (w + 40);
	if(x<0) x=0;
	if(y<0) y=0;
	XtSetArg(args[0], XtNx, x);
	XtSetArg(args[1], XtNy, y);
	XtSetValues(button_shell, args, 2);
	XtPopup(button_shell, XtGrabNone);
}

void restart(void)
{
	tournament.game_number = 1;
	tournament.points[BLACK] = 0;
	tournament.points[WHITE] = 0;

	AppendDialogText(tournament_display, "tournament restart:\n");

	if      (gammon_resource.moneygame) {
		sprintf (add_text, "money-game:\n");
	}
	else if (gammon_resource.bestof)
		sprintf(add_text, "tournament: best of %d   ", tournament.winning_point);
	else if (gammon_resource.winat)
		sprintf(add_text, "tournament: win at %d points   ",tournament.winning_point);
	AppendDialogText(tournament_display, add_text);
	AppendDialogText(tournament_display, "black points: 0, white points: 0");

	XGammonAppTournamentLoop();	/* bad, but works at least a little */
}

void set_game_kind(int to)
{
	if      (to == HUMAN_VS_COMPI) {
		Player[0].type = HUMAN;
		Player[1].type = COMPUTER;
	}
	else if (to == COMPI_VS_COMPI) {
		Player[0].type = COMPUTER;
		Player[1].type = COMPUTER;
	}
	else if (to == HUMAN_VS_HUMAN) {
		Player[0].type = HUMAN;
		Player[1].type = HUMAN;
	}
}
