/*
 * @(#)Hexagons.c
 *
 * Copyright 1994 - 2025  David A. Bagley, bagleyd AT verizon.net
 *
 * 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 the author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * 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.
 */

/* Methods file for Hexagons */

#include "file.h"
#include "rngs.h"
#include "sound.h"
#include "HexagonsP.h"

#ifndef PICTURE
#if 1
#define PICTURE ""
#else
#ifdef WINVER
#define PICTURE "picture"
#else
#ifdef HAVE_XPM
#define PICTURE "./mandrill.xpm"
#else
#define PICTURE "./mandrill.xbm"
#endif
#endif
#endif
#endif

#ifdef WINVER
#define hasValue(x) (*(x))
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "C:/ProgramData/wpuzzles/whexagons.ini"
#endif

#define SECTION "setup"
#else
#define hasValue(x) ((x && *(x)))
#include "picture.h"
#include "pixmaps/grain_lr.xbm"
#include "pixmaps/grain_tlbr.xbm"
#include "pixmaps/grain_trbl.xbm"

#if defined( USE_SOUND ) && defined( USE_NAS )
Display *dsp;
#endif

#ifndef LOGPATH
#ifdef VMS
#define LOGPATH "SYS$SCRATCH:"
#else
#define LOGPATH "/usr/tmp"
#endif
#endif

static Boolean setValuesPuzzle(Widget current, Widget request, Widget renew);
static void quitPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void destroyPuzzle(Widget old);
static void resizePuzzle(HexagonsWidget w);
static void sizePuzzle(HexagonsWidget w);
static void initializePuzzle(Widget request, Widget renew);
static void exposePuzzle(Widget renew, XEvent *event, Region region);
static void hidePuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void selectPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void releasePuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void clearWithQueryPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void clearWithDoubleClickPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void getPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void writePuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void undoPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void redoPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void clearPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void solvePuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void cornersPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void speedUpPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void slowDownPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void toggleSoundPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void enterPuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void leavePuzzle(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);

static void movePuzzleTl(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTop(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTr(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBl(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBr(HexagonsWidget w,
	XEvent *event, char **args, int nArgs);

static char translations[] =
"<KeyPress>q: Quit()\n\
 Ctrl<KeyPress>C: Quit()\n\
 <KeyPress>osfCancel: Hide()\n\
 <KeyPress>Escape: Hide()\n\
 <KeyPress>osfEscape: Hide()\n\
 Ctrl<KeyPress>[: Hide()\n\
 <KeyPress>0x1B: Hide()\n\
 <KeyPress>0x2E: Speed()\n\
 <KeyPress>0x3E: Speed()\n\
 <KeyPress>0x3C: Slow()\n\
 <KeyPress>0x2C: Slow()\n\
 Shift<KeyPress>2: Sound()\n\
 <KeyPress>Home: MoveTl()\n\
 <KeyPress>KP_7: MoveTl()\n\
 <KeyPress>R7: MoveTl()\n\
 <KeyPress>Up: MoveTop()\n\
 <KeyPress>osfUp: MoveTop()\n\
 <KeyPress>KP_Up: MoveTop()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\n\
 <KeyPress>Prior: MoveTr()\n\
 <KeyPress>KP_9: MoveTr()\n\
 <KeyPress>R9: MoveTr()\n\
 <KeyPress>Left: MoveLeft()\n\
 <KeyPress>osfLeft: MoveLeft()\n\
 <KeyPress>KP_Left: MoveLeft()\n\
 <KeyPress>KP_4: MoveLeft()\n\
 <KeyPress>R10: MoveLeft()\n\
 <KeyPress>Right: MoveRight()\n\
 <KeyPress>osfRight: MoveRight()\n\
 <KeyPress>KP_Right: MoveRight()\n\
 <KeyPress>KP_6: MoveRight()\n\
 <KeyPress>R12: MoveRight()\n\
 <KeyPress>End: MoveBl()\n\
 <KeyPress>KP_1: MoveBl()\n\
 <KeyPress>R13: MoveBl()\n\
 <KeyPress>Down: MoveBottom()\n\
 <KeyPress>osfDown: MoveBottom()\n\
 <KeyPress>KP_Down: MoveBottom()\n\
 <KeyPress>KP_2: MoveBottom()\n\
 <KeyPress>R14: MoveBottom()\n\
 <KeyPress>Next: MoveBr()\n\
 <KeyPress>KP_3: MoveBr()\n\
 <KeyPress>R15: MoveBr()\n\
 <Btn1Down>: Select()\n\
 <Btn1Up>: Release()\n\
 <Btn3Down>: ClearMaybe()\n\
 <Btn3Down>(2+): Clear2()\n\
 <Btn4Down>: MoveTop()\n\
 <Btn5Down>: MoveBottom()\n\
 <KeyPress>g: Get()\n\
 <KeyPress>w: Write()\n\
 <KeyPress>u: Undo()\n\
 <KeyPress>r: Redo()\n\
 <KeyPress>c: Clear()\n\
 <KeyPress>z: Randomize()\n\
 <KeyPress>s: Solve()\n\
 <KeyPress>m: Corners()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsList[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "MoveTl", (XtActionProc) movePuzzleTl},
	{(char *) "MoveTop", (XtActionProc) movePuzzleTop},
	{(char *) "MoveTr", (XtActionProc) movePuzzleTr},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBl", (XtActionProc) movePuzzleBl},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleBottom},
	{(char *) "MoveBr", (XtActionProc) movePuzzleBr},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "ClearMaybe", (XtActionProc) clearWithQueryPuzzle},
	{(char *) "Clear2", (XtActionProc) clearWithDoubleClickPuzzle},
	{(char *) "Get", (XtActionProc) getPuzzle},
	{(char *) "Write", (XtActionProc) writePuzzle},
	{(char *) "Undo", (XtActionProc) undoPuzzle},
	{(char *) "Redo", (XtActionProc) redoPuzzle},
	{(char *) "Clear", (XtActionProc) clearPuzzle},
	{(char *) "Randomize", (XtActionProc) randomizePuzzle},
	{(char *) "Solve", (XtActionProc) solvePuzzle},
	{(char *) "Corners", (XtActionProc) cornersPuzzle},
	{(char *) "Speed", (XtActionProc) speedUpPuzzle},
	{(char *) "Slow", (XtActionProc) slowDownPuzzle},
	{(char *) "Sound", (XtActionProc) toggleSoundPuzzle},
	{(char *) "Enter", (XtActionProc) enterPuzzle},
	{(char *) "Leave", (XtActionProc) leavePuzzle}
};

static XtResource resources[] =
{
	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
	 XtOffset(HexagonsWidget, core.width),
	 XtRString, (caddr_t) "388"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(HexagonsWidget, core.height),
	 XtRString, (caddr_t) "302"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(HexagonsWidget, hexagons.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(HexagonsWidget, hexagons.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNtileColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(HexagonsWidget, hexagons.tileColor),
	 XtRString, (caddr_t) "gray75" /*XtDefaultForeground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(HexagonsWidget, hexagons.frameColor),
	 XtRString, (caddr_t) "wheat4" /*XtDefaultForeground*/},
	{XtNtextColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(HexagonsWidget, hexagons.textColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultBackground*/},
	{XtNstippleFrame, XtCStippleFrame, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.stippleFrame),
	 XtRString, (caddr_t) "TRUE"},
	{XtNinstall, XtCInstall, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.install),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpicture, XtCPicture, XtRString, sizeof (String),
	 XtOffset(HexagonsWidget, hexagons.picture),
	 XtRString, (caddr_t) PICTURE},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(HexagonsWidget, hexagons.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNbumpSound, XtCBumpSound, XtRString, sizeof (String),
	 XtOffset(HexagonsWidget, hexagons.bumpSound),
	 XtRString, (caddr_t) BUMPSOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(HexagonsWidget, hexagons.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNsizeX, XtCSizeX, XtRInt, sizeof (int),
	 XtOffset(HexagonsWidget, hexagons.sizeX),
	 XtRString, (caddr_t) "3"}, /* DEFAULT_TILESX */
	{XtNsizeY, XtCSizeY, XtRInt, sizeof (int),
	 XtOffset(HexagonsWidget, hexagons.sizeY),
	 XtRString, (caddr_t) "3"}, /* DEFAULT_TILESY */
	{XtNcorners, XtCCorners, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.corners),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_CORNERS */
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(HexagonsWidget, hexagons.base),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_BASE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(HexagonsWidget, hexagons.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(HexagonsWidget, hexagons.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(HexagonsWidget, hexagons.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(HexagonsWidget, hexagons.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(HexagonsWidget, hexagons.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(HexagonsWidget, hexagons.select),
	 XtRCallback, (caddr_t) NULL}
};

HexagonsClassRec hexagonsClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Hexagons",	/* class name */
		sizeof (HexagonsRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) initializePuzzle,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		actionsList,	/* actions */
		XtNumber(actionsList),	/* num actions */
		resources,	/* resources */
		XtNumber(resources),	/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		(XtWidgetProc) destroyPuzzle,	/* destroy */
		(XtWidgetProc) resizePuzzle,	/* resize */
		(XtExposeProc) exposePuzzle,	/* expose */
		(XtSetValuesFunc) setValuesPuzzle,	/* set values */
		NULL,		/* set values hook */
		XtInheritSetValuesAlmost,	/* set values almost */
		NULL,		/* get values hook */
		NULL,		/* accept focus */
		XtVersion,	/* version */
		NULL,		/* callback private */
		translations,	/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	}
};

WidgetClass hexagonsWidgetClass = (WidgetClass) & hexagonsClassRec;

void
setPuzzle(HexagonsWidget w, int reason)
{
	hexagonsCallbackStruct cb;

	cb.reason = reason;
	XtCallCallbacks((Widget) w, (char *) XtNselectCallback, &cb);
}
#endif

static void
loadFont(HexagonsWidget w)
{
#ifndef WINVER
	Display *display = XtDisplay(w);
	const char *altfontname = "-*-times-*-r-*-*-*-180-*";
	char buf[512];

	if (w->hexagons.fontInfo) {
		XUnloadFont(XtDisplay(w), w->hexagons.fontInfo->fid);
		XFreeFont(XtDisplay(w), w->hexagons.fontInfo);
	}
	if (w->hexagons.font && (w->hexagons.fontInfo =
			XLoadQueryFont(display, w->hexagons.font)) == NULL) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 512,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->hexagons.font, altfontname);
#else
		(void) sprintf(buf,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->hexagons.font, altfontname);
#endif
		DISPLAY_WARNING(buf);
		if ((w->hexagons.fontInfo = XLoadQueryFont(display,
				altfontname)) == NULL) {
#ifdef HAVE_SNPRINTF
			(void) snprintf(buf, 512,
				"Cannot open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
#else
			(void) sprintf(buf,
				"Cannot open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
#endif
			DISPLAY_WARNING(buf);
		}
	}
	if (w->hexagons.fontInfo) {
		w->hexagons.digitOffset.x = XTextWidth(w->hexagons.fontInfo, "8", 1)
			/ 2;
		w->hexagons.digitOffset.y = w->hexagons.fontInfo->max_bounds.ascent
			/ 2;
	} else
#endif
	{
		w->hexagons.digitOffset.x = 3;
		w->hexagons.digitOffset.y = 4;
	}
}

#ifndef LOGFILE
#define LOGFILE "hexagons.log"
#endif

static const double sqrt_3 = 1.73205080756887729352744634150587237;
static Point hexagonUnit[MAX_ORIENT][7] =
{
	{
		{0, 0},
		{2, 0},
		{1, 1},
		{-1, 1},
		{-2, 0},
		{-1, -1},
		{1, -1}
	},
	{
		{0, 0},
		{1, 1},
		{0, 2},
		{-1, 1},
		{-1, -1},
		{0, -2},
		{1, -1}
	}
};
static Point hexagonList[MAX_ORIENT][7];

static HexagonsStack undo = {NULL, NULL, NULL, 0};
static HexagonsStack redo = {NULL, NULL, NULL, 0};

static void
checkTiles(HexagonsWidget w)
{
	char *buf1 = NULL, *buf2 = NULL;

	if (w->hexagons.sizeX < MIN_TILES) {
		intCat(&buf1,
			"Smallest number of hexagons in X direction out of bounds, use at least ",
			MIN_TILES);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_TILESX);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->hexagons.sizeX = DEFAULT_TILESX;
	}
	if (w->hexagons.sizeY < MIN_TILES) {
		intCat(&buf1,
			"Largest number of hexagons in Y direction out of bounds, use at least ",
			MIN_TILES);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_TILESY);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->hexagons.sizeY = DEFAULT_TILESY;
	}
#if 0
	if (w->hexagons.delay < 0) {
		intCat(&buf1, "Delay cannot be negative (",
			w->hexagons.delay);
		stringCat(&buf2, buf1, "), taking absolute value");
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		w->hexagons.delay = -w->hexagons.delay;
	}
#endif
	if (w->hexagons.base < MIN_BASE || w->hexagons.base > MAX_BASE) {
		intCat(&buf1, "Base out of bounds, use ", MIN_BASE);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAX_BASE);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_BASE);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->hexagons.base = DEFAULT_BASE;
	}
}

/* This is fast for small i, a -1 is returned for negative i. */
static int
SQRT(const int i)
{
	int j = 0;

	while (j * j <= i)
		j++;
	return (j - 1);
}

static void
swap(int *a, int *b)
{
	int c;

	c = *b;
	*b = *a;
	*a = c;
}

#ifdef DEBUG
static int
positionInRow(HexagonsWidget w, int position, int posRow)
{
	/* Untested */
	return (posRow <= w->hexagons.sizeY - 1) ?
		(position - w->hexagons.sizeX * posRow -
		posRow * (posRow - 1) / 2) :
		(position - w->hexagons.sizeY * (posRow - w->hexagons.sizeY) -
		4 * w->hexagons.sizeX * (w->hexagons.sizeX - 1) / 2 +
		(2 * w->hexagons.sizeX - posRow - 2) *
		(2 * w->hexagons.sizeX - posRow - 1) / 2 - 1);
}
#endif

static int
positionFromRow(HexagonsWidget w, int rowPosition, int posRow)
{
	return (posRow <= w->hexagons.sizeY - 1) ?
		(w->hexagons.sizeX * posRow + posRow * (posRow - 1) / 2 +
		rowPosition) :
		(w->hexagons.sizeY * (2 * posRow - w->hexagons.sizeY + 1) -
		w->hexagons.sizeX * (posRow + 1) + 4 *
		w->hexagons.sizeX * (w->hexagons.sizeX - 1) / 2 -
		(2 * w->hexagons.sizeX - posRow - 2) *
		(2 * w->hexagons.sizeX - posRow - 1) / 2 + 1 + rowPosition);
}

static int
toPosition(HexagonsWidget w, int row, int trbl)
{
	return ((row < w->hexagons.sizeY) ?
		positionFromRow(w, trbl, row) :
		positionFromRow(w, trbl, row) - row + w->hexagons.sizeY - 1);
}

int
toRow(HexagonsWidget w, const int pos)
{
	return (pos <= w->hexagons.sizeCenter) ?
		(1 + SQRT(1 + 8 * (pos + w->hexagons.sizeX *
		(w->hexagons.sizeX - 1) / 2))) / 2 - w->hexagons.sizeX :
		w->hexagons.sizeX + 2 * w->hexagons.sizeY - 2 -
		(1 + SQRT(1 + 8 * (w->hexagons.sizeSize - 1 +
		w->hexagons.sizeX * (w->hexagons.sizeX - 1) / 2 - pos))) / 2;
}

/* Passing row so there is no sqrt calculation again */
int
toTrBl(HexagonsWidget w, const int pos, const int posRow)
{
	return (pos <= w->hexagons.sizeCenter) ?
		(pos + w->hexagons.sizeX * (w->hexagons.sizeX - 1) / 2) -
		(posRow + w->hexagons.sizeX) *
		(posRow + w->hexagons.sizeX - 1) / 2 :
		w->hexagons.sizeY + w->hexagons.sizeX - 2 -
		(w->hexagons.sizeSize - 1 +
		w->hexagons.sizeX * (w->hexagons.sizeX - 1) / 2 - pos -
		(2 * w->hexagons.sizeY + w->hexagons.sizeX - posRow - 2) *
		(2 * w->hexagons.sizeY + w->hexagons.sizeX - posRow - 3) / 2);
}

int
toTlBr(HexagonsWidget w, const int pos, const int posRow)
{
	return (pos <= w->hexagons.sizeCenter) ?
		-(1 + pos + w->hexagons.sizeX * (w->hexagons.sizeX - 1) / 2) +
		(posRow + w->hexagons.sizeX + 1) *
		(posRow + w->hexagons.sizeX) / 2 :
		w->hexagons.sizeY + w->hexagons.sizeX - 1 +
		(w->hexagons.sizeSize - 1 +
		w->hexagons.sizeX * (w->hexagons.sizeX - 1) / 2 - pos -
		(2 * w->hexagons.sizeY + w->hexagons.sizeX - posRow - 1) *
		(2 * w->hexagons.sizeY + w->hexagons.sizeX - posRow - 2) / 2);
}

static int
toDirHeading(int direction) {
	return (direction > 0 && direction < 4) ? HIGH : LOW;
}

static int
toRowType(int direction) {
	switch (direction) {
	case TR:
	case BL:
		return TRBL;
	case TL:
	case BR:
		return TLBR;
	case LEFT:
	case RIGHT:
		return ROW;
	default:
		return -1;
	}
}

static int
cartesianX(HexagonsWidget w, int pos, int row)
{
	return w->hexagons.tileSize.x / 4 - 1 + (2 * toTrBl(w, pos, row) +
		w->hexagons.sizeY - row) * w->hexagons.offset.x / 2 - 2 +
		w->hexagons.puzzleOffset.x;
}

static int
cartesianY(HexagonsWidget w, int row)
{
	return row * (3 * w->hexagons.tileSize.y / 2 + w->hexagons.delta.y) +
		w->hexagons.delta.y + w->hexagons.puzzleOffset.y;
}

int
tileNFrom(HexagonsWidget w, int pos, int n, int rowType, int direction)
{
	int offset = (direction == HIGH) ? -n : n;

	if (rowType == ROW) /* This one is easy */
		return (pos + offset);
	else {
		int row = toRow(w, pos);
		int trbl = toTrBl(w, pos, row);
		if (rowType == TRBL)
			return toPosition(w, row + offset, trbl);
		else /*if (rowType == TLBR)*/
			return toPosition(w, row + offset, trbl + offset);
	}
}

static int
tileNFromSpace(HexagonsWidget w, int n, int rowType, int direction)
{
	return tileNFrom(w, w->hexagons.spacePosition[HIGH], n, rowType, direction);

}

Boolean
checkSolved(const HexagonsWidget w)
{
	int i;

	for (i = 1; i < w->hexagons.sizeSize -
			((w->hexagons.corners) ? CORNERS : NOCORN); i++)
		if (w->hexagons.tileOfPosition[i - 1] != i)
			return FALSE;
	return TRUE;
}

#ifdef ANIMATE
static Boolean
bresenhamLine(int startX, int startY, int endX, int endY,
		int * pixX, int * pixY, int * pixNo)
{
	int pix = 0;
	int ex = endX - startX;
	int ey = endY - startY;
	int dx, dy, error;

	(*pixNo)++;
	if (ex > 0) {
		dx = 1;
	} else if (ex < 0) {
		dx = -1;
		ex = -ex;
	} else {
		dx = 0;
	}
	if (ey > 0) {
		dy = 1;
	} else if (ey < 0) {
		dy = -1;
		ey = -ey;
	} else {
		dy = 0;
	}
	*pixX = startX;
	*pixY = startY;
	if (ex > ey) {
		error = 2 * ey - ex;
		while (pix != *pixNo) {
			if (error >= 0) {
				error -= 2 * ex;
				*pixY += dy;
			}
			error += 2 * ey;
			*pixX += dx;
			pix++;
			if (*pixX == endX)
				return True;
		}
		return False;
	} else {
		error = 2 * ex - ey;
		while (pix != *pixNo) {
			if (error >= 0) {
				error -= 2 * ey;
				*pixX += dx;
			}
			error += 2 * ex;
			*pixY += dy;
			pix++;
			if (*pixY == endY)
				return True;
		}
		return False;
	}
}
#endif

static int
int2String(HexagonsWidget w, char *buf, int number, int base, Boolean capital)
{
	int digit, mult = base, last, position;
	int a, i, j, s, r;

	if (capital) {
		a = 'A', i = 'I', j = 'J', s = 'S', r = 'R';
	} else {
		a = 'a', i = 'i', j = 'j', s = 's', r = 'r';
	}
	if (number < 0) {
		char *buff;

		intCat(&buff, "int2String: 0 > ", number);
		DISPLAY_WARNING(buff);
		free(buff);
		return 0;
	}
	last = 1;
	while (number >= mult) {
		last++;
		mult *= base;
	}
	for (position = 0; position < last; position++) {
		mult /= base;
		digit = number / mult;
		number -= digit * mult;
		buf[position] = (char) (digit + '0');
		if (buf[position] > '9') {	/* ASCII */
			buf[position] = (char) (buf[position] + (a - '9' - 1));
		} else if (buf[position] < '0') {	/* EBCDIC */
			buf[position] = (char) (buf[position] + (a - '9' - 1));
			if (buf[position] > i)
				buf[position] = (char) (buf[position] + (j - i - 1));
			if (buf[position] > r)
				buf[position] = (char) (buf[position] + (s - r - 1));
		}
	}
	buf[last] = '\0';
	return last;
}

static void
fill3DHexagon(HexagonsWidget w, Pixmap dr, GC gc, GC darkerGC, GC brighterGC,
	Point * list, Boolean raised, Boolean corners)
{
	GC currentGC = (raised) ? gc : darkerGC;
	Point tempList[7];
	int i;

	for (i = 0; i < 7; i++) {
		tempList[i].x = ((i == 0) ? 0 : tempList[i - 1].x) + list[i].x;
		tempList[i].y = ((i == 0) ? 0 : tempList[i - 1].y) + list[i].y;
	}

	POLYGON(w, dr, currentGC, currentGC, list, 6, True, False);
	if (!corners) {
		DRAWLINE(w, dr, currentGC,
			tempList[4].x, tempList[4].y,
			tempList[5].x, tempList[5].y);
		DRAWLINE(w, dr, currentGC,
			tempList[1].x, tempList[1].y,
			tempList[2].x, tempList[2].y);
	}
	if (!raised && corners) {
		currentGC = darkerGC;
		DRAWLINE(w, dr, currentGC,
			tempList[4].x, tempList[4].y,
			tempList[5].x, tempList[5].y);
		DRAWLINE(w, dr, currentGC,
			tempList[4].x + 1, tempList[4].y,
			tempList[5].x + 1, tempList[5].y);
	}
	currentGC = (raised) ? brighterGC : darkerGC;
	DRAWLINE(w, dr, currentGC,
		tempList[5].x, tempList[5].y,
		tempList[0].x, tempList[0].y);
	DRAWLINE(w, dr, currentGC,
		tempList[0].x, tempList[0].y,
		tempList[1].x, tempList[1].y);
	DRAWLINE(w, dr, currentGC,
		tempList[0].x, tempList[0].y + 1,
		tempList[1].x, tempList[1].y + 1);
	if (corners) {
		DRAWLINE(w, dr, currentGC,
			tempList[5].x, tempList[5].y + 1,
			tempList[0].x, tempList[0].y + 1);
		DRAWLINE(w, dr, currentGC,
			tempList[5].x, tempList[5].y + 2,
			tempList[0].x, tempList[0].y + 2);
	} else {
		DRAWLINE(w, dr, currentGC,
			tempList[5].x + 1, tempList[5].y,
			tempList[0].x + 1, tempList[0].y);
		DRAWLINE(w, dr, currentGC,
			tempList[5].x + 2, tempList[5].y,
			tempList[0].x + 2, tempList[0].y);
	}
	currentGC = (raised) ? darkerGC : gc;
	DRAWLINE(w, dr, currentGC,
		tempList[2].x, tempList[2].y, tempList[3].x, tempList[3].y);
	DRAWLINE(w, dr, currentGC,
		tempList[3].x, tempList[3].y, tempList[4].x, tempList[4].y);
	DRAWLINE(w, dr, currentGC,
		tempList[3].x, tempList[3].y - 1,
		tempList[4].x, tempList[4].y - 1);
	if (corners) {
		DRAWLINE(w, dr, currentGC,
			tempList[1].x, tempList[1].y,
			tempList[2].x, tempList[2].y);
		DRAWLINE(w, dr, currentGC,
			tempList[1].x - 1, tempList[1].y,
			tempList[2].x - 1, tempList[2].y);
		DRAWLINE(w, dr, currentGC,
			tempList[2].x, tempList[2].y - 1,
			tempList[3].x, tempList[3].y - 1);
		DRAWLINE(w, dr, currentGC,
			tempList[2].x, tempList[2].y - 2,
			tempList[3].x, tempList[3].y - 2);
		if (raised) {
			currentGC = brighterGC;
			DRAWLINE(w, dr, currentGC,
				tempList[4].x, tempList[4].y,
				tempList[5].x, tempList[5].y);
			DRAWLINE(w, dr, currentGC,
				tempList[4].x + 1, tempList[4].y,
				tempList[5].x + 1, tempList[5].y);
		}
	} else {
		DRAWLINE(w, dr, currentGC,
			tempList[2].x - 1, tempList[2].y,
			tempList[3].x - 1, tempList[3].y);
		DRAWLINE(w, dr, currentGC,
			tempList[2].x - 2, tempList[2].y,
			tempList[3].x - 2, tempList[3].y);
	}
}

static void
drawShadow(HexagonsWidget w, Pixmap dr, GC gc, int startX, int startY,
	Point * list, Boolean corners)
{
	Point tempList[6];
	int i;

	for (i = 0; i < 6; i++) {
		tempList[i].x = ((i == 0) ? startX : tempList[i - 1].x) +
			list[i].x;
		tempList[i].y = ((i == 0) ? startY : tempList[i - 1].y) +
			list[i].y;
	}
	DRAWLINE(w, dr, gc,
		tempList[4].x, tempList[4].y,
		tempList[5].x, tempList[5].y);
	DRAWLINE(w, dr, gc,
		tempList[5].x, tempList[5].y,
		tempList[0].x, tempList[0].y);
	DRAWLINE(w, dr, gc,
		tempList[0].x, tempList[0].y,
		tempList[1].x, tempList[1].y);
	if (corners) {
		DRAWLINE(w, dr, gc,
			tempList[5].x, tempList[5].y + 1,
			tempList[0].x, tempList[0].y + 1);
		DRAWLINE(w, dr, gc,
			tempList[0].x, tempList[0].y,
			tempList[0].x, tempList[0].y + 2);
	} else {
		DRAWLINE(w, dr, gc,
			tempList[5].x + 1, tempList[5].y,
			tempList[0].x + 1, tempList[0].y);
		DRAWLINE(w, dr, gc,
			tempList[1].x, tempList[1].y,
			tempList[1].x, tempList[1].y + 2);
	}
}

static void
drawTile(HexagonsWidget w, int pos, Boolean blank, Boolean erase,
		int pressedOffset, int offsetX, int offsetY)
{
	Pixmap *dr;
	Pixmap adr = 0;
	int dx, dy, row;
	int corners = (w->hexagons.corners) ? 1 : 0;
	GC tileGC, textGC, highlightGC, shadowGC;

	/*dr = &(w->hexagons.bufferTiles[pressedOffset]);*/
	dr = &adr;
	if (erase) {
		tileGC = w->hexagons.inverseGC[2];
		textGC = w->hexagons.inverseGC[2];
	} else {
		tileGC = w->hexagons.tileGC[1];
		textGC = w->hexagons.textGC;
	}

	row = toRow(w, pos);
	dx = cartesianX(w, pos, row) + offsetX + pressedOffset;
	dy = cartesianY(w, row) + offsetY + pressedOffset;
	if (corners) {
		hexagonList[corners][0].x = dx;
		hexagonList[corners][0].y = dy;
	} else {
		hexagonList[corners][0].x = dx - w->hexagons.offset.x / 4;
		hexagonList[corners][0].y = dy +
			(w->hexagons.tileSize.y + w->hexagons.delta.y) / 4;
	}
	if (blank) {
		/*POLYGON(w, *dr, tileGC, textGC,
			hexagonList[corners], 6, True, False);*/
		highlightGC = w->hexagons.inverseGC[2];
		shadowGC = w->hexagons.inverseGC[2];
	} else {
		highlightGC = w->hexagons.tileGC[0];
		shadowGC = w->hexagons.tileGC[2];
	}
	if (pressedOffset != 0) {
		drawShadow(w, *dr, shadowGC,
			-pressedOffset, -pressedOffset,
			hexagonList[corners], w->hexagons.corners);
	}
#if 0
#include "mandrill.xbm"
	if (!blank) {
		/* HACK */
		Display *display = XtDisplay(w);
		Window window = XtWindow(w);
		Pixmap graypix = XCreateBitmapFromData(display, window,
			(char *) mandrill_bits, mandrill_width, mandrill_height);
		/*XSetStipple(display, tileGC, w->hexagons.bufferTiles[0]);*/ /*Bad Match, whats not matching?*/
		XSetStipple(display, tileGC, graypix);
		/*XSetFillStyle(display, tileGC, FillStippled);*/
		XSetFillStyle(display, tileGC, FillOpaqueStippled);
		XSetTSOrigin(display, tileGC, 100, 100);
		XFreePixmap(display, graypix);
		/*XSetFillStyle(display, tileGC, FillSolid);*/
	}
#endif
	fill3DHexagon(w, *dr, tileGC, shadowGC,
		highlightGC,
		hexagonList[corners],
		pressedOffset == 0, w->hexagons.corners);
	if (!blank) {
		int i = 0, digitOffsetX = 0;
		int tile = w->hexagons.tileOfPosition[pos];
		char buf[65];

		(void) int2String(w, buf, tile, w->hexagons.base, True);
		while (tile >= 1) {
			tile /= w->hexagons.base;
			digitOffsetX += w->hexagons.digitOffset.x;
			i++;
		}
		DRAWTEXT(w, *dr, textGC,
			dx - digitOffsetX,
			dy + w->hexagons.tileSize.y + w->hexagons.digitOffset.y,
			buf, i);
	}
}

static void
drawAllBufferedTiles(const HexagonsWidget w)
{
	int k;

	for (k = 0; k < w->hexagons.sizeSize; k++)
		drawTile(w, k, (w->hexagons.tileOfPosition[k] <= 0),
			(w->hexagons.tileOfPosition[k] <= 0), False, 0, 0);
}

void
drawAllTiles(const HexagonsWidget w)
{
	int k;

	for (k = 0; k < w->hexagons.sizeSize; k++)
		drawTile(w, k, (w->hexagons.tileOfPosition[k] <= 0),
			(w->hexagons.tileOfPosition[k] <= 0), False, 0, 0);
}

static int
findSpaceType(HexagonsWidget w, int p1, int p2, int r1, int r2)
{
	int pos1 = p1, pos2 = p2, row1 = r1, row2 = r2;

	if (row1 == row2 && (pos1 == pos2 + 1 || pos1 == pos2 - 1))
		return (ROW);
	else if (row1 == row2 - 1) {
		swap(&row1, &row2);
		swap(&pos1, &pos2);
	}
	if (row1 == row2 + 1) {
		if (row1 <= w->hexagons.sizeY - 1) {
			if (pos2 == pos1 - w->hexagons.sizeX - row1)
				return (TLBR);
			else if (pos2 == pos1 - w->hexagons.sizeX - row1 + 1)
				return (TRBL);
		} else {	/* row1 > w->hexagons.sizeY - 1 */
			if (pos2 == pos1 - 2 * w->hexagons.sizeY -
					w->hexagons.sizeX + row1 + 1)
				return (TLBR);
			else if (pos2 == pos1 - 2 * w->hexagons.sizeY -
					w->hexagons.sizeX + row1 + 2)
				return (TRBL);
		}
	}
	return (-1);
}

static void
findMovableTile(HexagonsWidget w, int pos, int posRow, int spaceType, int side,
	int *tilePos, int *tileRow)
{
	switch (spaceType) {
	case TRBL:
		if (side == HIGH) {
			*tileRow = posRow;
			*tilePos = pos + 1;
		} else {	/* side == LOW */
			*tileRow = posRow - 1;
			*tilePos = (posRow <= w->hexagons.sizeY - 1) ?
				pos - w->hexagons.sizeX - posRow :
				pos - 2 * w->hexagons.sizeY -
				w->hexagons.sizeX + posRow + 1;
		}
		break;
	case TLBR:
		if (side == HIGH) {
			*tileRow = posRow;
			*tilePos = pos - 1;
		} else {	/* side == LOW */
			*tileRow = posRow - 1;
			*tilePos = (posRow <= w->hexagons.sizeY - 1) ?
				pos - w->hexagons.sizeX - posRow + 1 :
				pos - 2 * w->hexagons.sizeY -
				w->hexagons.sizeX + posRow + 2;
		}
		break;
	case ROW:
		if (side == HIGH) {
			*tileRow = posRow + 1;
			*tilePos = (posRow < w->hexagons.sizeY - 1) ?
				pos + w->hexagons.sizeX + posRow :
				pos + 2 * w->hexagons.sizeY +
				w->hexagons.sizeX - posRow - 3;
		} else {	/* side == LOW */
			*tileRow = posRow - 1;
			*tilePos = (posRow <= w->hexagons.sizeY - 1) ?
				pos - w->hexagons.sizeX - posRow :
				pos - 2 * w->hexagons.sizeY -
				w->hexagons.sizeX + posRow + 1;
		}
		break;
	default:
		{
			char *buf;

			intCat(&buf,
				"findMovableTile: spaceType \n",
				spaceType);
			DISPLAY_WARNING(buf);
			free(buf);
			*tileRow = 0;
			*tilePos = 0;
		}
	}
}

static int
nextToWall(HexagonsWidget w, int pos, int posRow, int spaceType)
{
	switch (spaceType) {
	case TRBL:
		if (posRow < w->hexagons.sizeY &&
				pos == w->hexagons.sizeX * posRow +
				posRow * (posRow - 1) / 2)
			return (HIGH);
		else if (posRow >= w->hexagons.sizeY &&
				pos == w->hexagons.sizeY *
				(2 * posRow - w->hexagons.sizeY + 3) -
				w->hexagons.sizeX * posRow - 2 -
				posRow + 4 * w->hexagons.sizeX *
				(w->hexagons.sizeX - 1) / 2 -
				(2 * w->hexagons.sizeX - posRow - 2) *
				(2 * w->hexagons.sizeX - posRow - 1) / 2)
			return (LOW);
		else
			return (-1);
	case TLBR:
		if (posRow < w->hexagons.sizeY && pos ==
				w->hexagons.sizeX * (posRow + 1) +
				posRow * (posRow + 1) / 2 - 1)
			return (HIGH);
		else if (posRow >= w->hexagons.sizeY &&
				pos == w->hexagons.sizeY *
				(2 * posRow - w->hexagons.sizeY + 1) -
				w->hexagons.sizeX * (posRow + 1) + 1 +
				4 * w->hexagons.sizeX *
				(w->hexagons.sizeX - 1) / 2 -
				(2 * w->hexagons.sizeX - posRow - 2) *
				(2 * w->hexagons.sizeX - posRow - 1) / 2)
			return (LOW);
		else
			return (-1);
	case ROW:
		if (posRow == 0)
			return (HIGH);
		else if (posRow == 2 * (w->hexagons.sizeY - 1))
			return (LOW);
		else
			return (-1);
	}
	return (-2);	/* Unknown space formation. */
}

static int
movableCornerTiles(HexagonsWidget w, int direction,
		int *position, int *posRow, int *space)
{
	int spaceType, highest, side = -1;

	highest = (w->hexagons.spacePosition[HIGH] >
		w->hexagons.spacePosition[LOW]) ? HIGH : LOW;
	spaceType = findSpaceType(w,
		w->hexagons.spacePosition[HIGH], w->hexagons.spacePosition[LOW],
		w->hexagons.spaceRow[HIGH], w->hexagons.spaceRow[LOW]);
	switch (spaceType) {
	case TRBL:
		if (direction == TR || direction == BL)
			return FALSE;
		side = nextToWall(w, w->hexagons.spacePosition[highest],
			w->hexagons.spaceRow[highest], spaceType);
		if (side != -1) {
			if ((side == HIGH && direction == RIGHT) ||
					(side == HIGH && direction == BR) ||
					(side == LOW && direction == LEFT) ||
					(side == LOW && direction == TL))
				return FALSE;
		} else
			side = (direction == TL || direction == LEFT);
		*space = (direction == BR || direction == LEFT);
		break;
	case TLBR:
		if (direction == TL || direction == BR)
			return FALSE;
		side = nextToWall(w, w->hexagons.spacePosition[highest],
			w->hexagons.spaceRow[highest], spaceType);
		if (side != -1) {
			if ((side == LOW && direction == TR) ||
					(side == LOW && direction == RIGHT) ||
					(side == HIGH && direction == BL) ||
					(side == HIGH && direction == LEFT))
				return FALSE;
		} else
			side = (direction == TR || direction == RIGHT);
		*space = (direction == RIGHT || direction == BL);
		break;
	case ROW:
		if (direction == LEFT || direction == RIGHT)
			return FALSE;
		side = nextToWall(w, w->hexagons.spacePosition[highest],
			w->hexagons.spaceRow[highest], spaceType);
		if (side != -1) {
			if ((side == LOW && direction == TR) ||
					(side == HIGH && direction == BR) ||
					(side == HIGH && direction == BL) ||
					(side == LOW && direction == TL))
				return FALSE;
		} else
			side = (direction == TR || direction == TL);
		*space = (direction == TR || direction == BR);
		break;
	default:
		{
			char * buf;

			intCat(&buf, "movableCornerTiles: spaceType ",
				spaceType);
			DISPLAY_WARNING(buf);
			free(buf);
			*space = 0;
		}
	}
	findMovableTile(w, w->hexagons.spacePosition[highest],
		w->hexagons.spaceRow[highest], spaceType, side, position,
		posRow);
	return TRUE;
}

static int
movableNoCornTile(HexagonsWidget w)
{
	int rowType = BLOCKED, l;

	/* Are the spaces in a "row" with the mouse click?
	 * (If two, then one clicked on a space). */
	for (l = 0; l < ROW_TYPES; l++) {
		if (w->hexagons.currentRow[l] == w->hexagons.spaceRow[l]) {
			if (rowType == BLOCKED) {
				rowType = l;
			} else {
				return SPACE;
			}
		}
	}
	return rowType;
}

#ifdef ANIMATE
static int
countTiles(HexagonsWidget w, int rowType)
{
	switch (rowType) {
	case TLBR:
		return ABS(w->hexagons.spaceRow[ROW] -
			w->hexagons.currentRow[ROW]);
	case TRBL:
		return ABS(w->hexagons.spaceRow[ROW] -
			w->hexagons.currentRow[ROW]);
	case ROW:
		return ABS(w->hexagons.spaceRow[TRBL] -
			w->hexagons.currentRow[TRBL]);
	default:
		{
			char * buf;

			intCat(&buf, "countTiles: rowType ", rowType);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
	return 0;
}

static int
dirTiles(HexagonsWidget w, int dir, int rowType)
{
	switch (rowType) {
	case TLBR:
		return (dir == HIGH) ? BR : TL;
	case TRBL:
		return (dir == HIGH) ? BL : TR;
	case ROW:
		return (dir == HIGH) ? RIGHT : LEFT;
	default:
		{
			char *buf;

			intCat(&buf, "dirTiles: rowType ", rowType);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
	return dir;
}

static void
animateSlide(HexagonsWidget w, int dir, int fast, Boolean logMoves)
{
	int aTile, numTiles, direction = dir, rowType;
	int fillBeginPos;
	int pos, posNext;

	rowType = movableNoCornTile(w);
	if (rowType < 0) {
		char *buf;

		intCat(&buf, "AnimateTiles: rowType ", rowType);
		DISPLAY_WARNING(buf);
		free(buf);
		numTiles = 0;
	} else {
		numTiles = countTiles(w, rowType);
		direction = dirTiles(w, dir, rowType);
	}
	if (numTiles < 0)
		numTiles = -numTiles;
	fillBeginPos = tileNFromSpace(w, numTiles, rowType, dir);
	{
		int pixNo = 0;
		int pixX = 0, pixY = 0;
		int fromdx = 0, fromdy = 0;
		int todx = 0, tody = 0;
		int dx, dy, row;
		int *rowPos;
		int i, k;
		Boolean slideDone = False;

		if (!(rowPos = (int *)
				malloc(sizeof (int) *
					(size_t) (numTiles + 1)))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		rowPos[1] = tileNFromSpace(w, 1, rowType, dir);
		rowPos[0] = w->hexagons.spacePosition[HIGH];
		for (i = 1; i < numTiles; i++) {
			rowPos[i + 1] = tileNFromSpace(w,
				i + 1, rowType, dir);
		}
		row = toRow(w, rowPos[1]);
		fromdx = cartesianX(w, rowPos[1], row);
		fromdy = cartesianY(w, row);
		row = toRow(w, rowPos[0]);
		todx = cartesianX(w, rowPos[0], row);
		tody = cartesianY(w, row);
		dx = todx - fromdx;
		dy = tody - fromdy;
		i = 0;
		while (!slideDone) {
			for (k = 1; k < numTiles + 1; k++)
				drawTile(w, rowPos[k],
					True, True, FALSE, pixX, pixY);
			slideDone = bresenhamLine(0, 0, dx, dy,
				&pixX, &pixY, &pixNo);
			for (k = 1; k < numTiles + 1; k++)
				drawTile(w, rowPos[k],
					False, False, FALSE, pixX, pixY);
			FLUSH(w);
			if (i % 8 == 0)
				Sleep((unsigned int) (w->hexagons.delay /
					fast));
			i++;
		}
		free(rowPos);
	}
	for (aTile = 0; aTile < numTiles; aTile++) {
		if (logMoves) {
			setPuzzle(w, ACTION_MOVED);
			setMove(&undo, direction);
			flushMoves(w, &redo, FALSE);
		}
		posNext = tileNFromSpace(w, aTile + 1, rowType, !dir);
	}
	pos = w->hexagons.spacePosition[HIGH];
	for (aTile = 0; aTile < numTiles; aTile++) {
		posNext = tileNFromSpace(w, aTile + 1, rowType, dir);
		w->hexagons.tileOfPosition[pos] =
			w->hexagons.tileOfPosition[posNext];
		pos = posNext;
	}
	w->hexagons.tileOfPosition[fillBeginPos] =
		w->hexagons.spacePosition[HIGH];
	w->hexagons.spacePosition[HIGH] = fillBeginPos;
	w->hexagons.tileOfPosition[fillBeginPos] = 0;
	w->hexagons.spaceRow[ROW] = toRow(w, fillBeginPos);
	w->hexagons.spaceRow[TRBL] =
		toTrBl(w, fillBeginPos, w->hexagons.spaceRow[ROW]);
	w->hexagons.spaceRow[TLBR] =
		toTlBr(w, fillBeginPos, w->hexagons.spaceRow[ROW]);
}
#endif

static void
resetTiles(HexagonsWidget w)
{
	int pos;
	int start13[] = {1, 5, 4, 2, 6, 3, 7};
	int start22[] = {4, 3, 1, 5, 2};
	int start2[] = {-1, 0};

	w->hexagons.sizeSize = w->hexagons.sizeY * w->hexagons.sizeY +
		(w->hexagons.sizeX - 1) * (w->hexagons.sizeY * 2 - 1);
	w->hexagons.sizeCenter = ((w->hexagons.sizeSize - 1) >> 1);
	if (w->hexagons.tileOfPosition)
		free(w->hexagons.tileOfPosition);
	if (!(w->hexagons.tileOfPosition = (int *)
			malloc(sizeof (int) * (size_t) w->hexagons.sizeSize))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}

	if (startPosition)
		free(startPosition);
	if (!(startPosition = (int *)
			malloc(sizeof (int) * (size_t) w->hexagons.sizeSize))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}

	/*w->hexagons.spacePosition[HIGH] = w->hexagons.sizeSize - 1;
	if (w->hexagons.corners) {
		w->hexagons.spaceRow[HIGH] = 2 * w->hexagons.sizeY - 2;
		if (w->hexagons.sizeX > 1 || w->hexagons.sizeY > 1) {
			w->hexagons.spacePosition[LOW] = w->hexagons.sizeSize - 2;
			w->hexagons.spaceRow[LOW] = 2 * w->hexagons.sizeY -
				((w->hexagons.sizeX == 1) ? 3 : 2);
			w->hexagons.tileOfPosition[w->hexagons.sizeSize - 2] = -1;
		}
	} else {
		w->hexagons.spaceRow[ROW] = w->hexagons.spaceRow[TRBL] =
			2 * w->hexagons.sizeY - 2;
		w->hexagons.spaceRow[TLBR] = w->hexagons.sizeY - 1;
	}
	w->hexagons.tileOfPosition[w->hexagons.sizeSize - 1] = 0;
	for (pos = 1; pos < w->hexagons.sizeSize - ((w->hexagons.corners) ? 1 : 0);
			pos++)
		w->hexagons.tileOfPosition[pos - 1] = pos;*/
#define OFFSET 1
#ifdef OFFSET
	if (w->hexagons.corners &&
			(w->hexagons.sizeX > 1 || w->hexagons.sizeY > 1)) {
		w->hexagons.spacePosition[HIGH] = 1;
		w->hexagons.spaceRow[HIGH] =
			(w->hexagons.sizeX == 1) ? 1 : 0;
		w->hexagons.spacePosition[LOW] = 0;
		w->hexagons.spaceRow[LOW] = 0;
		for (pos = 0; pos < 2; pos++)
			w->hexagons.tileOfPosition[pos] = start2[pos];
	} else {
		w->hexagons.spacePosition[HIGH] = 0;
		for (pos = 0; pos < 1; pos++)
			w->hexagons.tileOfPosition[pos] = start2[pos + 1];
		if (w->hexagons.corners) {
			w->hexagons.spaceRow[HIGH] = 0;
		} else {
			w->hexagons.spaceRow[ROW] = w->hexagons.spaceRow[TRBL] = 0;
			w->hexagons.spaceRow[TLBR] = w->hexagons.sizeX - 1;
		}
	}
#elif defined(FLIP)
#endif
	/* fix for unsolvable cases */
	if (w->hexagons.corners && w->hexagons.sizeX == 1 &&
			w->hexagons.sizeY == 3) {
		for (pos = 0; pos < w->hexagons.sizeSize - 2; pos++)
			w->hexagons.tileOfPosition[pos + 2] = start13[pos];
	} else if (w->hexagons.corners && w->hexagons.sizeX == 2 &&
			w->hexagons.sizeY == 2) {
		for (pos = 0; pos < w->hexagons.sizeSize - 2; pos++)
			w->hexagons.tileOfPosition[pos + 2] = start22[pos];
	} else {
		for (pos = 0; pos < w->hexagons.sizeSize - 1 -
				((w->hexagons.corners) ? 1 : 0); pos++)
			w->hexagons.tileOfPosition[pos + 1 +
				((w->hexagons.corners) ? 1 : 0)] = pos + 1;
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->hexagons.currentPosition = -1;
	w->hexagons.started = FALSE;
}

static void
drawFrameLR(const HexagonsWidget w, Pixmap dr)
{
	GC gc = w->hexagons.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->hexagons.stippleFrame && w->hexagons.grainLR != (Pixmap) NULL) {
		XSetStipple(display, w->hexagons.frameGC, w->hexagons.grainLR);
		XSetFillStyle(display, w->hexagons.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->hexagons.frameGC, FillSolid);
#endif
	/* Top */
	tempList[0].x = w->hexagons.sumX4 - w->hexagons.frameThickness.x / 2 - 2;
	tempList[0].y = w->hexagons.offsetY - w->hexagons.frameThickness.y - 1;
	tempList[1].x = w->hexagons.sum3X4 + w->hexagons.frameThickness.x / 2 + 2;
	tempList[1].y = w->hexagons.offsetY - w->hexagons.frameThickness.y - 1;
	tempList[2].x = w->hexagons.sum3X4 + 2;
	tempList[2].y = w->hexagons.offsetY - 1;
	tempList[3].x = w->hexagons.sumX4 - 2;
	tempList[3].y = w->hexagons.offsetY - 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
	/* Bottom */
	tempList[0].x = w->hexagons.sumX4 - w->hexagons.frameThickness.x / 2 - 2;
	tempList[0].y = w->hexagons.sumY + w->hexagons.frameThickness.y + 1;
	tempList[1].x = w->hexagons.sum3X4 + w->hexagons.frameThickness.x / 2 + 2;
	tempList[1].y = w->hexagons.sumY + w->hexagons.frameThickness.y + 1;
	tempList[2].x = w->hexagons.sum3X4 + 2;
	tempList[2].y = w->hexagons.sumY + 1;
	tempList[3].x = w->hexagons.sumX4 - 2;
	tempList[3].y = w->hexagons.sumY + 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
}

static void
drawFrameTLBR(const HexagonsWidget w, Pixmap dr)
{
	GC gc = w->hexagons.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->hexagons.stippleFrame && w->hexagons.grainTLBR != (Pixmap) NULL) {
		XSetStipple(display, w->hexagons.frameGC, w->hexagons.grainTLBR);
		XSetFillStyle(display, w->hexagons.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->hexagons.frameGC, FillSolid);
#endif
	/* Top Right */
	tempList[0].x = w->hexagons.sum3X4 + w->hexagons.frameThickness.x / 2 + 2;
	tempList[0].y = w->hexagons.offsetY - w->hexagons.frameThickness.y - 1;
	tempList[1].x = w->hexagons.sum3X4 + 2;
	tempList[1].y = w->hexagons.offsetY - 1;
	tempList[2].x = w->hexagons.sumX + 2;
	tempList[2].y = w->hexagons.sumY2;
	tempList[3].x = w->hexagons.sumX + w->hexagons.frameThickness.x - 2;
	tempList[3].y = w->hexagons.sumY2;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
	/* Bottom Left */
	tempList[0].x = w->hexagons.sumX4 - w->hexagons.frameThickness.x / 2 - 2;
	tempList[0].y = w->hexagons.sumY + w->hexagons.frameThickness.y + 1;
	tempList[1].x = w->hexagons.sumX4 - 2;
	tempList[1].y = w->hexagons.sumY + 1;
	tempList[2].x = w->hexagons.offsetX - 2;
	tempList[2].y = w->hexagons.sumY2;
	tempList[3].x = w->hexagons.offsetX - w->hexagons.frameThickness.x + 2;
	tempList[3].y = w->hexagons.sumY2;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
}

static void
drawFrameTRBL(const HexagonsWidget w, Pixmap dr)
{
	GC gc = w->hexagons.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->hexagons.stippleFrame && w->hexagons.grainTRBL != (Pixmap) NULL) {
		XSetStipple(display, w->hexagons.frameGC, w->hexagons.grainTRBL);
		XSetFillStyle(display, w->hexagons.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->hexagons.frameGC, FillSolid);
#endif
	/* Top Left */
	tempList[0].x = w->hexagons.sumX4 - w->hexagons.frameThickness.x / 2 - 2;
	tempList[0].y = w->hexagons.offsetY - w->hexagons.frameThickness.y - 1;
	tempList[1].x = w->hexagons.sumX4 - 2;
	tempList[1].y = w->hexagons.offsetY - 1;
	tempList[2].x = w->hexagons.offsetX - 2;
	tempList[2].y = w->hexagons.sumY2;
	tempList[3].x = w->hexagons.offsetX + 2 - w->hexagons.frameThickness.x;
	tempList[3].y = w->hexagons.sumY2;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
	/* Bottom Right */
	tempList[0].x = w->hexagons.sum3X4 + w->hexagons.frameThickness.x / 2 + 2;
	tempList[0].y = w->hexagons.sumY + w->hexagons.frameThickness.y + 1;
	tempList[1].x = w->hexagons.sum3X4 + 2;
	tempList[1].y = w->hexagons.sumY + 1;
	tempList[2].x = w->hexagons.sumX + 2;
	tempList[2].y = w->hexagons.sumY2;
	tempList[3].x = w->hexagons.sumX + w->hexagons.frameThickness.x - 2;
	tempList[3].y = w->hexagons.sumY2;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
}

static void
drawFrame(HexagonsWidget w, Pixmap dr, Boolean all, Boolean focus)
{
	Point tempList[7];
	GC gc = (focus) ? w->hexagons.inverseGC[1] :
		w->hexagons.inverseGC[3];

	w->hexagons.sumX = (w->hexagons.sizeX + w->hexagons.sizeY) * w->hexagons.offset.x
		- w->hexagons.tileSize.x / 2 - 2 * w->hexagons.delta.x - 1;
	w->hexagons.sumY = w->hexagons.sizeY * (3 * w->hexagons.tileSize.y
		+ 2 * w->hexagons.delta.y) - w->hexagons.tileSize.y - 1;
	w->hexagons.offsetX = w->hexagons.puzzleOffset.x;
	w->hexagons.offsetY = w->hexagons.puzzleOffset.y;
	w->hexagons.sumX4 = w->hexagons.sizeY * w->hexagons.offset.x / 2
		+ w->hexagons.offsetX - w->hexagons.tileSize.x / 4 + 1;
	w->hexagons.sum3X4 = w->hexagons.sizeX * w->hexagons.offset.x
		+ w->hexagons.sumX4 - 12;
	w->hexagons.sumY2 = w->hexagons.sumY / 2 + w->hexagons.offsetY;
	w->hexagons.sumX += w->hexagons.offsetX + w->hexagons.sizeX / 2;
	w->hexagons.sumY += w->hexagons.offsetY;
	w->hexagons.offsetX += -1 - w->hexagons.sizeX / 2;
	if (all) {
		if (w->hexagons.offsetY > 0) {
			FILLRECTANGLE(w, dr, w->hexagons.textGC,
				0, 0, w->core.width, w->hexagons.offsetY);
		}
		if (w->hexagons.sumY < w->core.height) {
			FILLRECTANGLE(w, dr, w->hexagons.textGC,
				0, w->hexagons.sumY + 1,
				w->core.width, w->core.height - w->hexagons.sumY);
		}
		if (w->hexagons.offsetX > 0) {
			FILLRECTANGLE(w, dr, w->hexagons.textGC,
				0, 0, w->hexagons.offsetX, w->core.height);
		}
		if (w->hexagons.sumX < w->core.width) {
			FILLRECTANGLE(w, dr, w->hexagons.textGC,
				w->hexagons.sumX + 1, 0,
				w->core.width - w->hexagons.sumX - 1, w->core.height);
		}
		tempList[0].x = w->hexagons.offsetX;
		tempList[0].y = w->hexagons.offsetY;
		tempList[1].x = w->hexagons.sumX4;
		tempList[1].y = w->hexagons.offsetY;
		tempList[2].x = w->hexagons.offsetX;
		tempList[2].y = w->hexagons.sumY2;
		tempList[3].x = tempList[0].x;
		tempList[3].y = tempList[0].y;
		POLYGON(w, dr, w->hexagons.textGC, w->hexagons.textGC,
			tempList, 3, True, True);
		tempList[0].x = w->hexagons.sumX + 1;
		tempList[0].y = w->hexagons.offsetY;
		tempList[1].x = w->hexagons.sumX + 1;
		tempList[1].y = w->hexagons.sumY2;
		tempList[2].x = w->hexagons.sumX;
		tempList[2].y = w->hexagons.sumY2;
		tempList[3].x = w->hexagons.sum3X4;
		tempList[3].y = w->hexagons.offsetY;
		tempList[4].x = tempList[0].x;
		tempList[4].y = tempList[0].y;
		POLYGON(w, dr, w->hexagons.textGC, w->hexagons.textGC,
			tempList, 4, True, True);
		tempList[0].x = w->hexagons.offsetX;
		tempList[0].y = w->hexagons.sumY + 1;
		tempList[1].x = w->hexagons.offsetX;
		tempList[1].y = w->hexagons.sumY2;
		tempList[2].x = w->hexagons.sumX4;
		tempList[2].y = w->hexagons.sumY;
		tempList[3].x = w->hexagons.sumX4;
		tempList[3].y = w->hexagons.sumY + 1;
		tempList[4].x = tempList[0].x;
		tempList[4].y = tempList[0].y;
		POLYGON(w, dr, w->hexagons.textGC, w->hexagons.textGC,
			tempList, 4, True, True);
		tempList[0].x = w->hexagons.sumX + 1;
		tempList[0].y = w->hexagons.sumY + 1;
		tempList[1].x = w->hexagons.sum3X4;
		tempList[1].y = w->hexagons.sumY + 1;
		tempList[2].x = w->hexagons.sum3X4;
		tempList[2].y = w->hexagons.sumY;
		tempList[3].x = w->hexagons.sumX;
		tempList[3].y = w->hexagons.sumY2;
		tempList[4].x = w->hexagons.sumX + 1;
		tempList[4].y = w->hexagons.sumY2;
		tempList[5].x = tempList[0].x;
		tempList[5].y = tempList[0].y;
		POLYGON(w, dr, w->hexagons.textGC, w->hexagons.textGC,
			tempList, 5, True, True);
	}
	if (all) {
		tempList[0].x = w->hexagons.sumX4;
		tempList[0].y = w->hexagons.offsetY;
		tempList[1].x = w->hexagons.sum3X4;
		tempList[1].y = w->hexagons.offsetY;
		tempList[2].x = w->hexagons.sumX;
		tempList[2].y = w->hexagons.sumY2;
		tempList[3].x = w->hexagons.sum3X4;
		tempList[3].y = w->hexagons.sumY;
		tempList[4].x = w->hexagons.sumX4;
		tempList[4].y = w->hexagons.sumY;
		tempList[5].x = w->hexagons.offsetX;
		tempList[5].y = w->hexagons.sumY2;
		tempList[6].x = tempList[0].x;
		tempList[6].y = tempList[0].y;
		POLYGON(w, dr, w->hexagons.inverseGC[2], gc,
			tempList, 6, True, True);
	} else {
		GC brighterGC = (focus) ? w->hexagons.inverseGC[1] :
			w->hexagons.inverseGC[2];
		GC darkerGC = (focus) ? w->hexagons.inverseGC[3] :
			w->hexagons.inverseGC[2];

		tempList[0].x = w->hexagons.sum3X4;
		tempList[0].y = w->hexagons.offsetY;
		tempList[1].x = w->hexagons.sumX;
		tempList[1].y = w->hexagons.sumY2;
		tempList[2].x = w->hexagons.sum3X4;
		tempList[2].y = w->hexagons.sumY;
		tempList[3].x = w->hexagons.sumX4;
		tempList[3].y = w->hexagons.sumY;
		POLYLINE(w, dr, brighterGC, tempList, 4, True);
		tempList[0].x = w->hexagons.sumX4;
		tempList[0].y = w->hexagons.sumY;
		tempList[1].x = w->hexagons.offsetX;
		tempList[1].y = w->hexagons.sumY2;
		tempList[2].x = w->hexagons.sumX4;
		tempList[2].y = w->hexagons.offsetY;
		tempList[3].x = w->hexagons.sum3X4;
		tempList[3].y = w->hexagons.offsetY;
		POLYLINE(w, dr, darkerGC, tempList, 4, True);
		tempList[0].x = w->hexagons.sum3X4 - 1;
		tempList[0].y = w->hexagons.offsetY;
		tempList[1].x = w->hexagons.sumX - 1;
		tempList[1].y = w->hexagons.sumY2;
		tempList[2].x = w->hexagons.sum3X4 - 1;
		tempList[2].y = w->hexagons.sumY;
		tempList[3].x = w->hexagons.sumX4 + 1;
		tempList[3].y = w->hexagons.sumY;
		POLYLINE(w, dr, brighterGC, tempList, 4, True);
		tempList[0].x = w->hexagons.sumX4 + 1;
		tempList[0].y = w->hexagons.sumY;
		tempList[1].x = w->hexagons.offsetX + 1;
		tempList[1].y = w->hexagons.sumY2;
		tempList[2].x = w->hexagons.sumX4 + 1;
		tempList[2].y = w->hexagons.offsetY;
		tempList[3].x = w->hexagons.sum3X4 - 1;
		tempList[3].y = w->hexagons.offsetY;
		POLYLINE(w, dr, darkerGC, tempList, 4, True);
		tempList[0].x = w->hexagons.sum3X4;
		tempList[0].y = w->hexagons.offsetY - 1;
		tempList[1].x = w->hexagons.sumX + 1;
		tempList[1].y = w->hexagons.sumY2;
		tempList[2].x = w->hexagons.sum3X4;
		tempList[2].y = w->hexagons.sumY + 1;
		tempList[3].x = w->hexagons.sumX4;
		tempList[3].y = w->hexagons.sumY + 1;
		POLYLINE(w, dr, brighterGC, tempList, 4, True);
		tempList[0].x = w->hexagons.sumX4;
		tempList[0].y = w->hexagons.sumY + 1;
		tempList[1].x = w->hexagons.offsetX - 1;
		tempList[1].y = w->hexagons.sumY2;
		tempList[2].x = w->hexagons.sumX4;
		tempList[2].y = w->hexagons.offsetY - 1;
		tempList[3].x = w->hexagons.sum3X4;
		tempList[3].y = w->hexagons.offsetY - 1;
		POLYLINE(w, dr, darkerGC, tempList, 4, True);
	}
	drawFrameLR(w, dr);
	drawFrameTLBR(w, dr);
	drawFrameTRBL(w, dr);
}

static void
eraseFrame(HexagonsWidget w, Pixmap dr)
{
	FILLRECTANGLE(w, dr, w->hexagons.inverseGC[2],
		0, 0, w->core.width, w->core.height);
}

static void
moveNoTiles(HexagonsWidget w)
{
	setPuzzle(w, ACTION_IGNORE);
}

static void
moveCornerTiles(HexagonsWidget w, int from, int posRow, int space, int fast)
{
	int fromTile, nextPos, row;
	int dx, dy, s0x, s0y, s1x, s1y, cx, cy;

	fromTile = w->hexagons.tileOfPosition[from];
	nextPos = w->hexagons.spacePosition[space];
	row = toRow(w, from);
	dx = cartesianX(w, from, row);
	dy = cartesianY(w, row);
	row = toRow(w, w->hexagons.spacePosition[space]);
	s0x = cartesianX(w, w->hexagons.spacePosition[space], row);
	s0y = cartesianY(w, row);
	row = toRow(w, w->hexagons.spacePosition[!space]);
	s1x = cartesianX(w, w->hexagons.spacePosition[!space], row);
	s1y = cartesianY(w, row);
	cx = (s0x + s1x - 2 * dx) / 3;
	cy = (s0y + s1y - 2 * dy) / 3;
	if (fast == INSTANT) {
		drawTile(w, from, True, True, FALSE, 0, 0);
		drawTile(w, from, False, False, FALSE, cx, cy);
		FLUSH(w);
#ifdef USE_SOUND
		if (w->hexagons.sound) {
			playSound(BUMPSOUND);
		}
#endif
		drawTile(w, from, True, True, FALSE, cx, cy);
		drawTile(w, from, False, False, FALSE, s0x - dx, s0y - dy);
	} else {
		Boolean slideDone = False;
		int pixX = 0, pixY = 0, pixNo = 0, i = 0;

		while (!slideDone) {
			drawTile(w, from, True, True, FALSE, pixX, pixY);
			slideDone = bresenhamLine(0, 0, cx, cy,
				&pixX, &pixY, &pixNo);
			drawTile(w, from, False, False, FALSE, pixX, pixY);
			FLUSH(w);
			if (i % 8 == 0)
				Sleep((unsigned int) (w->hexagons.delay /
					fast));
			i++;
		}
#ifdef USE_SOUND
		if (w->hexagons.sound) {
			playSound(BUMPSOUND);
		}
#endif
		slideDone = False;
		i = 0;
		pixNo = 0;
		while (!slideDone) {
			drawTile(w, from, True, True, FALSE, pixX, pixY);
			slideDone = bresenhamLine(cx, cy, s0x - dx, s0y - dy,
				&pixX, &pixY, &pixNo);
			drawTile(w, from, False, False, FALSE, pixX, pixY);
			FLUSH(w);
			if (i % 8 == 0)
				Sleep((unsigned int) (w->hexagons.delay /
					fast));
			i++;
		}
	}
	w->hexagons.tileOfPosition[from] =
		w->hexagons.tileOfPosition[w->hexagons.spacePosition[space]];
	w->hexagons.spacePosition[space] = from;
	w->hexagons.spaceRow[space] = posRow;
	w->hexagons.tileOfPosition[nextPos] = fromTile;
	FLUSH(w);
#ifdef USE_SOUND
	if (w->hexagons.sound) {
		playSound(BUMPSOUND);
	}
#endif
	if (fast != INSTANT) {
		Sleep((unsigned int) (10 * w->hexagons.delay / fast));
	}
}

static void
moveNoCornTiles(HexagonsWidget w, int from, int posRow, int fast)
{
#ifdef ANIMATE
	if (fast != INSTANT && w->hexagons.delay > 0) {
		w->hexagons.currentPosition = from;
		w->hexagons.currentRow[ROW] = toRow(w, from);
		w->hexagons.currentRow[TRBL] =
			toTrBl(w, from, w->hexagons.currentRow[ROW]);
		w->hexagons.currentRow[TLBR] =
			toTlBr(w, from, w->hexagons.currentRow[ROW]);
		if (w->hexagons.currentPosition <
				w->hexagons.spacePosition[HIGH]) {
			animateSlide(w, HIGH, fast, False);
		} else if (w->hexagons.currentPosition >
				w->hexagons.spacePosition[HIGH]) {
			animateSlide(w, LOW, fast, False);
		} else
			return;
		w->hexagons.currentPosition = -1;
	} else
#endif
	{
		int tempTile;

		tempTile = w->hexagons.tileOfPosition[from];
		w->hexagons.tileOfPosition[from] =
			w->hexagons.tileOfPosition[w->hexagons.spacePosition[HIGH]];
		w->hexagons.tileOfPosition[w->hexagons.spacePosition[HIGH]] = tempTile;
		drawTile(w, w->hexagons.spacePosition[HIGH],
			False, False, FALSE, 0, 0);
		w->hexagons.spacePosition[HIGH] = from;
		w->hexagons.spaceRow[ROW] = posRow;
		w->hexagons.spaceRow[TRBL] = toTrBl(w, from, posRow);
		w->hexagons.spaceRow[TLBR] = toTlBr(w, from, posRow);
		drawTile(w, w->hexagons.spacePosition[HIGH],
			True, True, FALSE, 0, 0);
	}
#ifdef USE_SOUND
	if (w->hexagons.sound) {
		playSound(BUMPSOUND);
	}
#endif
}

static int
findTileTriangle(HexagonsWidget w, int pI, int pJ, int pK,
		int rI, int rJ, int rK)
{
	int temp = 0, k = 0, row1 = 0, row2 = 0, pos;
	Boolean found = True;

	if (rI == rJ) {
		if (pI == pJ - 1)
			temp = pJ;
		else if (pI == pJ + 1)
			temp = pI;
		else
			found = False;
		k = pK;
		row1 = rI;
		row2 = rK;
	} else if (rJ == rK) {
		if (pJ == pK - 1)
			temp = pK;
		else if (pJ == pK + 1)
			temp = pJ;
		else
			found = False;
		k = pI;
		row1 = rJ;
		row2 = rI;
	} else if (rK == rI) {
		if (pK == pI - 1)
			temp = pI;
		else if (pK == pI + 1)
			temp = pK;
		else
			found = False;
		k = pJ;
		row1 = rK;
		row2 = rJ;
	}
	if (!found)
		return (0);
	pos = -1;
	if (row1 == row2 + 1) {
		if (row1 <= w->hexagons.sizeY - 1)
			pos = temp - w->hexagons.sizeX - row1;
		else		/* row1 > w->hexagons.sizeY - 1 */
			pos = temp - 2 * w->hexagons.sizeY - w->hexagons.sizeX +
				row1 + 1;
	} else if (row1 == row2 - 1) {
		if (row1 < w->hexagons.sizeY - 1)
			pos = temp + w->hexagons.sizeX + row1;
		else		/* row1 >= w->hexagons.sizeY - 1 */
			pos = temp + 2 * (w->hexagons.sizeY - 1) +
				(w->hexagons.sizeX - 1) - row1;
	}
	if (k == pos)
		return (1);
	return (0);
}

static int
findDir(HexagonsWidget w, int posTile, int posSpace, int rowTile, int rowSpace)
{
	if (rowTile == rowSpace) {
		if (posTile > posSpace)
			return LEFT;
		else
			return RIGHT;
	} else if (toTrBl(w, posTile, rowTile) == toTrBl(w, posSpace, rowSpace)) {
		if (posTile > posSpace)
			return TR;
		else
			return BL;
	} else {
		/* if (toTlBr(w, posTile, rowTile) == toTlBr(w, posSpace, rowSpace)) */
		if (posTile > posSpace)
			return TL;
		else
			return BR;
	}
}

#ifdef DEBUG
static int
withinFrame(HexagonsWidget w, int x, int y, int dx, int dy)
{
	return
		(x < dx + w->hexagons.tileSize.x / 2 &&
		x > dx - w->hexagons.tileSize.x / 2 &&
		w->hexagons.tileSize.y * (x - dx) < w->hexagons.tileSize.x * (y - dy) &&
		w->hexagons.tileSize.y * (dx - x) < w->hexagons.tileSize.x * (y - dy) &&
		w->hexagons.tileSize.y * (x - dx + 2 * w->hexagons.tileSize.x) >
		w->hexagons.tileSize.x * (y - dy) &&
		w->hexagons.tileSize.y * (dx - x + 2 * w->hexagons.tileSize.x) >
		w->hexagons.tileSize.x * (y - dy));
}
#endif

static int
moveCornerTilesDir(HexagonsWidget w, int direction, int fast)
{
	int position, posRow, space;

	if (movableCornerTiles(w, direction, &position, &posRow, &space)) {
		moveCornerTiles(w, position, posRow, (w->hexagons.spacePosition[HIGH] <
			w->hexagons.spacePosition[LOW]) ? !space : space, fast);
		return TRUE;
	}
	return FALSE;
}

static Boolean
checkMoveNoCornTilesDir(HexagonsWidget w, int direction)
{
	switch (direction) {
	case TR:
		return (w->hexagons.spaceRow[ROW] !=
				2 * w->hexagons.sizeY - 2 &&
				w->hexagons.spaceRow[TLBR] !=
				w->hexagons.sizeX + w->hexagons.sizeY - 2);
	case RIGHT:
		return (w->hexagons.spaceRow[TRBL] != 0 &&
				w->hexagons.spaceRow[TLBR] !=
				w->hexagons.sizeX + w->hexagons.sizeY - 2);
	case BR:
		return (w->hexagons.spaceRow[ROW] != 0 &&
				w->hexagons.spaceRow[TRBL] != 0);
	case BL:
		return (w->hexagons.spaceRow[ROW] != 0 &&
				w->hexagons.spaceRow[TLBR] != 0);
	case LEFT:
		return (w->hexagons.spaceRow[TLBR] != 0 &&
				w->hexagons.spaceRow[TRBL] !=
				w->hexagons.sizeX + w->hexagons.sizeY - 2);
	case TL:
		return (w->hexagons.spaceRow[ROW] !=
				2 * w->hexagons.sizeY - 2 &&
				w->hexagons.spaceRow[TRBL] !=
				w->hexagons.sizeX + w->hexagons.sizeY - 2);
	default:
		return False;
	}
}

static int
nextRowNoCorn(HexagonsWidget w, int direction)
{
	switch (direction) {
	case TR:
	case TL:
		return w->hexagons.spaceRow[ROW] + 1;
	case RIGHT:
	case LEFT:
		return w->hexagons.spaceRow[ROW];
	case BR:
	case BL:
		return w->hexagons.spaceRow[ROW] - 1;
	default:
		return -1;
	}
}

static int
moveNoCornTilesDir(HexagonsWidget w, int direction, int fast)
{
	if (checkMoveNoCornTilesDir(w, direction)) {
		int rowType = toRowType(direction);
		int heading = toDirHeading(direction);
		int from = tileNFromSpace(w, 1, rowType, heading);
		int to = nextRowNoCorn(w, direction);

		moveNoCornTiles(w, from, to, fast);
		return TRUE;
	}
	return FALSE;
}

static int
moveTilesDir(HexagonsWidget w, int direction, int fast)
{
	if (w->hexagons.corners)
		return moveCornerTilesDir(w, direction, fast);
	else
		return moveNoCornTilesDir(w, direction, fast);
}

int
movePuzzleDir(HexagonsWidget w, const int direction, const int fast)
{
	if (moveTilesDir(w, direction, fast)) {
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, direction);
		flushMoves(w, &redo, FALSE);
		return TRUE;
	}
	return FALSE;
}

#ifndef WINVER
static
#endif
int
movePuzzle(HexagonsWidget w, const int direction, const int control)
{
	int reason;

	if (control) {
		reason = ACTION_IGNORE;
		switch (direction) {
		case TL:
		case TOP:
		case TR:
			reason = ACTION_INCY;
			break;
		case RIGHT:
			reason = ACTION_INCX;
			break;
		case BL:
		case BOTTOM:
		case BR:
			if (w->hexagons.sizeY <= MIN_TILES +
					w->hexagons.corners)
				return FALSE;
			reason = ACTION_DECY;
			break;
		case LEFT:
			if (w->hexagons.sizeX <= MIN_TILES)
				return FALSE;
			reason = ACTION_DECX;
			break;
		default:
			{
				char *buf;

				intCat(&buf,
					"movePuzzle: direction ",
					direction);
				DISPLAY_WARNING(buf);
				free(buf);
			}
		}
		setPuzzle(w, reason);
		return TRUE;
	}
	if (checkSolved(w)) {
		moveNoTiles(w);
		return FALSE;
	}
	if (direction == TOP || direction == BOTTOM) {
		setPuzzle(w, ACTION_AMBIGUOUS);
		return FALSE;
	}
	if (!movePuzzleDir(w, direction, NORMAL)) {
		setPuzzle(w, ACTION_BLOCKED);
		return FALSE;
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
	return TRUE;
}

static int
exchangeTiles(HexagonsWidget w, int pos1, int pos2)
{
	int tempTile;

	if (w->hexagons.tileOfPosition[pos1] <= 0 ||
			w->hexagons.tileOfPosition[pos2] <= 0)
		return FALSE;
	tempTile = w->hexagons.tileOfPosition[pos1];
	w->hexagons.tileOfPosition[pos1] = w->hexagons.tileOfPosition[pos2];
	w->hexagons.tileOfPosition[pos2] = tempTile;
	return TRUE;
}

static void
discreteNoCornMoves(HexagonsWidget w, int dir, int fast)
{
	int rowType, orient;
	char *buf;

	rowType = movableNoCornTile(w);
	if (rowType < 0) {
		intCat(&buf, "discreteMoves: rowType ", rowType);
			DISPLAY_WARNING(buf);
		free(buf);
		DISPLAY_WARNING(buf);
	}
	if (dir == HIGH) {
		orient = (rowType == ROW) ? w->hexagons.spaceRow[ROW] :
			w->hexagons.spaceRow[ROW] - 1;
		moveNoCornTiles(w, tileNFromSpace(w, 1, rowType, HIGH),
			orient, fast);
		setPuzzle(w, ACTION_MOVED);
		switch (rowType) {
		case TLBR:
			setMove(&undo, BR);
			flushMoves(w, &redo, FALSE);
			return;
		case TRBL:
			setMove(&undo, BL);
			flushMoves(w, &redo, FALSE);
			return;
		case ROW:
			setMove(&undo, RIGHT);
			flushMoves(w, &redo, FALSE);
			return;
		default:
			{
				intCat(&buf,
					"DiscreteNoCornTiles: rowType ",
						rowType);
				DISPLAY_WARNING(buf);
				free(buf);
				return;
			}
		}
	} else { /* w->hexagons.currentPosition > w->hexagons.spacePosition[HIGH] */
		orient = (rowType == ROW) ? w->hexagons.spaceRow[ROW] :
			w->hexagons.spaceRow[ROW] + 1;
		moveNoCornTiles(w, tileNFromSpace(w, 1, rowType, LOW),
			orient, fast);
		setPuzzle(w, ACTION_MOVED);
		switch (rowType) {
		case TLBR:
			setMove(&undo, TL);
			flushMoves(w, &redo, FALSE);
			return;
		case TRBL:
			setMove(&undo, TR);
			flushMoves(w, &redo, FALSE);
			return;
		case ROW:
			setMove(&undo, LEFT);
			flushMoves(w, &redo, FALSE);
			return;
		default:
			{
				intCat(&buf,
					"DiscreteNoCornTiles: rowType ",
					rowType);
				DISPLAY_WARNING(buf);
				free(buf);
				return;
			}
		}
	}
}

static int
positionToTile(HexagonsWidget w, int positionX, int positionY, int *row)
{
	int i, j, k, modI, modJ, rowPos;
	int x = positionX - w->hexagons.puzzleOffset.x;
	int y = positionY - w->hexagons.puzzleOffset.y;

	/* First convert x and y coordinates to hexagon grid.
	 * Keep in mind that the starting hexagon x position
	 * changes with "w->hexagons.sizeX % 2". */
	if (x < w->hexagons.tileSize.x / 4)
		return -1;
	i = 2 * (x - w->hexagons.tileSize.x / 4) / w->hexagons.offset.x;
	j = 3 * (y - w->hexagons.delta.y) /
		(3 * w->hexagons.tileSize.y / 2 + w->hexagons.delta.y);
	modI = 2 * (x - w->hexagons.tileSize.x / 4) % w->hexagons.offset.x;
	modJ = 3 * (y - w->hexagons.delta.y) %
		(3 * w->hexagons.tileSize.y / 2 + w->hexagons.delta.y);
	*row = j / 3;		/* Approximate to a rectangle just for now */
	if (j % 3 == 0) {
		/* Then it is the hexagon near bottom or top point */
		if (((w->hexagons.sizeY - 1 + *row + i) & 1) == 1)	/* \ */
			*row -= (modJ * w->hexagons.offset.x < modI *
				(3 * w->hexagons.tileSize.y / 2 + w->hexagons.delta.y));
		else		/* / */
			*row -= (modJ * w->hexagons.offset.x < (w->hexagons.offset.x - modI) *
				(3 * w->hexagons.tileSize.y / 2 + w->hexagons.delta.y));
	}
	if (i < (w->hexagons.sizeY - 1 + *row) % 2 || *row < 0 ||
			*row > 2 * (w->hexagons.sizeY - 1))
		return -1;
	k = (i - ((w->hexagons.sizeY - 1 + *row) % 2)) / 2;
	/* Map the hexagon grid to hexagon position in puzzle. */
	rowPos = (*row < w->hexagons.sizeY) ?
		k - (w->hexagons.sizeY - 1 - *row) / 2 :
		k + (w->hexagons.sizeY - 1 - *row) / 2;
	j = (*row < w->hexagons.sizeY) ?
		w->hexagons.sizeX - 1 + *row : 2 * (w->hexagons.sizeY - 1) +
		w->hexagons.sizeX - 1 - *row;
	if (rowPos < 0 || rowPos > j)
		return -1;
	return positionFromRow(w, rowPos, *row);
}

static int
movableCornerTile(HexagonsWidget w)
{
	/* (Clicked on a space?) */
	if (w->hexagons.currentPosition == w->hexagons.spacePosition[LOW] ||
			w->hexagons.currentPosition == w->hexagons.spacePosition[HIGH]) {
		return SPACE;
	}
	if (findTileTriangle(w,
			w->hexagons.currentPosition, w->hexagons.spacePosition[HIGH],
			w->hexagons.spacePosition[LOW], w->hexagons.currentRow[ROW],
			w->hexagons.spaceRow[HIGH], w->hexagons.spaceRow[LOW])) {
		return 0;
	}
	return BLOCKED;
}

static void
selectCornerTiles(HexagonsWidget w, int space)
{
	int rowType, orient;

	rowType = movableCornerTile(w);
	if (rowType < 0) {
		char *buf;

		intCat(&buf, "selectCornerTiles: rowType ", rowType);
		DISPLAY_WARNING(buf);
		free(buf);
		return;
	}
	orient = (w->hexagons.spacePosition[HIGH] <
		w->hexagons.spacePosition[LOW]) ? ((space == HIGH) ?
		LOW : HIGH) : space;
	setMove(&undo, findDir(w,
		w->hexagons.currentPosition, w->hexagons.spacePosition[orient],
		w->hexagons.currentRow[ROW], w->hexagons.spaceRow[orient]));
	flushMoves(w, &redo, FALSE);
	moveCornerTiles(w, w->hexagons.currentPosition,
			w->hexagons.currentRow[ROW], orient, NORMAL);
	setPuzzle(w, ACTION_MOVED);
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
selectNoCornTiles(HexagonsWidget w)
{
	if (w->hexagons.currentPosition < w->hexagons.spacePosition[HIGH]) {
#ifdef ANIMATE
		if (w->hexagons.delay > 0) {
			animateSlide(w, HIGH, NORMAL, True);
#ifdef USE_SOUND
			if (w->hexagons.sound) {
				playSound(BUMPSOUND);
			}
#endif
		} else
#endif
		{
			while (w->hexagons.currentPosition <
					w->hexagons.spacePosition[HIGH]) {
				discreteNoCornMoves(w, HIGH, NORMAL);
			}
		}
	} else { /* w->hexagons.currentPosition > w->hexagons.spacePosition[HIGH] */
#ifdef ANIMATE
		if (w->hexagons.delay > 0) {
			animateSlide(w, LOW, NORMAL, True);
#ifdef USE_SOUND
			if (w->hexagons.sound) {
				playSound(BUMPSOUND);
			}
#endif
		} else
#endif
		{
			while (w->hexagons.currentPosition > w->hexagons.spacePosition[HIGH]) {
				discreteNoCornMoves(w, LOW, NORMAL);
			}
		}
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
randomizeTiles(HexagonsWidget w)
{
	if (w->hexagons.currentPosition >= 0)
		return;
	w->hexagons.cheat = False;
	/* First interchange tiles an even number of times */
	if ((w->hexagons.sizeY > 1 && !w->hexagons.corners) ||
			(w->hexagons.sizeX > 2 && w->hexagons.sizeY > 1) ||
			(w->hexagons.sizeX > 1 && w->hexagons.sizeY > 2) ||
			(w->hexagons.sizeX == 1 && w->hexagons.sizeY > 3)) {
		int currentPos, randomPos;
		int count = 0;

		for (currentPos = 0; currentPos < w->hexagons.sizeSize; currentPos++) {
			randomPos = currentPos;
			while (currentPos == randomPos)
				randomPos = NRAND(w->hexagons.sizeSize);
			count += exchangeTiles(w, currentPos, randomPos);
		}
		if ((count & 1) == 1 && w->hexagons.corners) {
			if (w->hexagons.sizeX == 1) {
				if (!exchangeTiles(w, 3, 4) &&
						!exchangeTiles(w,
						w->hexagons.sizeSize - 4,
						w->hexagons.sizeSize - 5)) {
					DISPLAY_WARNING("randomizeTiles: should not get here");
				}
			} else if (!exchangeTiles(w, 0, 1) &&
					!exchangeTiles(w,
					w->hexagons.sizeSize - 2,
					w->hexagons.sizeSize - 1)) {
				DISPLAY_WARNING("randomizeTiles: should not get here");
			}
		}
		drawAllTiles(w);
	}
	/* Now move the spaces around randomly */
	if (w->hexagons.sizeY > 1 ||
			(!w->hexagons.corners && w->hexagons.sizeX > 1)) {
		int big = w->hexagons.sizeSize + NRAND(2);
		int lastDirection = -1;
		int randomDirection;

		setPuzzle(w, ACTION_RESET);
#ifdef DEBUG
		big = 3;
#endif
		if (big > 1000)
			big = 1000;
		while (big--) {
			randomDirection = NRAND(COORD);
#ifdef DEBUG
			Sleep(1);
#endif
			if ((randomDirection + COORD / 2) % COORD != lastDirection) {
				if (movePuzzleDir(w, randomDirection, INSTANT))
					lastDirection = randomDirection;
				else
					big++;
			}
		}
		flushMoves(w, &undo, TRUE);
		flushMoves(w, &redo, FALSE);
		setPuzzle(w, ACTION_RANDOMIZE);
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
readFile(HexagonsWidget w, FILE *fp, char *name)
{
	int c, i, sizeX, sizeY, corners, moves;
	char *buf1 = NULL, *buf2 = NULL;

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &corners) != 1) {
		(void) fprintf(stderr, "corrupt record, expecting an integer for corners\n");
		return;
	}
	if (w->hexagons.corners != (Boolean) corners) {
		setPuzzle(w, ACTION_CORNERS);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &sizeX) != 1) {
		(void) fprintf(stderr, "corrupt record, expecting an integer for sizeX\n");
		return;
	}
	if (sizeX >= MIN_TILES) {
		for (i = w->hexagons.sizeX; i < sizeX; i++) {
			setPuzzle(w, ACTION_INCX);
		}
		for (i = w->hexagons.sizeX; i > sizeX; i--) {
			setPuzzle(w, ACTION_DECX);
		}
	} else {
		stringCat(&buf1, name, " corrupted: sizeX ");
		intCat(&buf2, buf1, sizeX);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_TILES);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &sizeY) != 1) {
		(void) fprintf(stderr, "corrupt record, expecting an integer for sizeY\n");
		return;
	}
	if (sizeY >= MIN_TILES) {
		for (i = w->hexagons.sizeY; i < sizeY; i++) {
			setPuzzle(w, ACTION_INCY);
		}
		for (i = w->hexagons.sizeY; i > sizeY; i--) {
			setPuzzle(w, ACTION_DECY);
		}
	} else {
		stringCat(&buf1, name, " corrupted: sizeY ");
		intCat(&buf2, buf1, sizeY);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_TILES);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
#ifdef WINVER
	resetTiles(w);
#endif
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &moves) != 1) {
		(void) fprintf(stderr, "corrupt record, expecting an integer for moves\n");
		return;
	}
	if (!scanStartPosition(fp, w))
		return;
	setPuzzle(w, ACTION_RESTORE);
	setStartPosition(w);
	if (!scanMoves(fp, w, moves))
		return;
	(void) printf("%s: corners %s, sizeX %d, sizeY %d, moves %d\n",
		name, BOOL_STRING(corners), sizeX, sizeY, moves);
}

static void
getTiles(HexagonsWidget w)
{
	FILE *fp;
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname, *name;

	stringCat(&buf1, CURRENTDELIM, LOGFILE);
	lname = buf1;
	stringCat(&buf1, LOGPATH, FINALDELIM);
	stringCat(&buf2, buf1, LOGFILE);
	free(buf1);
	fname = buf2;
	/* Try current directory first. */
	name = lname;
	if ((fp = fopen(name, "r")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "r")) == NULL) {
			stringCat(&buf1, "Cannot read ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot read ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	readFile(w, fp, name);
	(void) fclose(fp);
	free(lname);
	free(fname);
	w->hexagons.cheat = True; /* Assume the worst. */
}

static void
writeTiles(HexagonsWidget w)
{
	FILE *fp;
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname, *name;

	stringCat(&buf1, CURRENTDELIM, LOGFILE);
	lname = buf1;
	stringCat(&buf1, LOGPATH, FINALDELIM);
	stringCat(&buf2, buf1, LOGFILE);
	free(buf1);
	fname = buf2;
	/* Try current directory first. */
	name = lname;
	if ((fp = fopen(lname, "w")) == NULL) {
		name = fname;
		if ((fp = fopen(fname, "w")) == NULL) {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	(void) fprintf(fp, "corners%c %d\n", SYMBOL,
		(w->hexagons.corners) ? 1 : 0);
	(void) fprintf(fp, "sizeX%c %d\n", SYMBOL, w->hexagons.sizeX);
	(void) fprintf(fp, "sizeY%c %d\n", SYMBOL, w->hexagons.sizeY);
	(void) fprintf(fp, "moves%c %d\n", SYMBOL,
		numMoves(&undo));
	printStartPosition(fp, w);
	printMoves(fp, &undo);
	(void) fclose(fp);
	(void) printf("Saved to %s\n", name);
	free(lname);
	free(fname);
}

static void
undoTiles(HexagonsWidget w)
{
	if (madeMoves(&undo) &&
			w->hexagons.currentPosition < 0) {
		int direction;

		getMove(&undo, &direction);
		setMove(&redo, direction);
		direction = (direction + (COORD / 2)) % COORD;
		if (moveTilesDir(w, direction, DOUBLE)) {
			setPuzzle(w, ACTION_UNDO);
		} else {
			char *buf1, *buf2;

			intCat(&buf1, "Move ", direction);
			stringCat(&buf2, buf1, " cannot be made");
			free(buf1);
			DISPLAY_WARNING(buf2);
			free(buf2);
		}
	}
}

static void
redoTiles(HexagonsWidget w)
{
	if (madeMoves(&redo) &&
			w->hexagons.currentPosition < 0) {
		int direction;

		getMove(&redo, &direction);
		setMove(&undo, direction);
		if (moveTilesDir(w, direction, DOUBLE)) {
			setPuzzle(w, ACTION_REDO);
		} else {
			char *buf1, *buf2;

			intCat(&buf1, "Move ", direction);
			stringCat(&buf2, buf1, " cannot be made");
			free(buf1);
			DISPLAY_WARNING(buf2);
			free(buf2);
		}
	}
}

static void
clearTiles(HexagonsWidget w)
{
	if (w->hexagons.currentPosition >= 0)
		return;
	resetTiles(w);
	drawAllTiles(w);
	setPuzzle(w, ACTION_RESET);
}

static void
solveTiles(HexagonsWidget w)
{
	if (checkSolved(w) || w->hexagons.currentPosition >= 0)
		return;
#ifdef TRY
	if (w->hexagons.sizeX == w->hexagons.sizeY)
		solveSomeTiles(w);
	else
#endif
	{
		setPuzzle(w, ACTION_SOLVE_MESSAGE);
	}
}

static void
cornersTiles(HexagonsWidget w)
{
	if (w->hexagons.corners || w->hexagons.sizeY > 1) {
		setPuzzle(w, ACTION_CORNERS);
	}
}

static void
speedTiles(HexagonsWidget w)
{
	w->hexagons.delay -= 5;
	if (w->hexagons.delay < 0)
		w->hexagons.delay = 0;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	setPuzzle(w, ACTION_SPEED);
#endif
}

static void
slowTiles(HexagonsWidget w)
{
	w->hexagons.delay += 5;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	setPuzzle(w, ACTION_SPEED);
#endif
}

static void
soundTiles(HexagonsWidget w)
{
	w->hexagons.sound = !w->hexagons.sound;
	setPuzzle(w, ACTION_SOUND);
}

#define BRIGHT_FACTOR 0.8
#define DARK_FACTOR 0.75

#ifdef WINVER
#define MAX_INTENSITY 0xFF
static int
brighter(const int light)
{
	int i = (int) ((1 - BRIGHT_FACTOR) * MAX_INTENSITY);
	int temp = light;

	if (temp < i)
		temp = i;
	return MIN(temp / BRIGHT_FACTOR, MAX_INTENSITY);
}

static int
darker(const int light)
{
 return (int) (light * DARK_FACTOR);
}

static void
setValuesPuzzle(HexagonsWidget w)
{
	struct tagColor {
		int red, green, blue;
	} color;
	char szBuf[80];

	w->hexagons.sizeX = (int) GetPrivateProfileInt(SECTION,
		"sizeX", DEFAULT_TILESX, INIFILE);
	w->hexagons.sizeY = (int) GetPrivateProfileInt(SECTION,
		"sizeY", DEFAULT_TILESY, INIFILE);
	w->hexagons.corners = (BOOL) GetPrivateProfileInt(SECTION,
		"corners", DEFAULT_CORNERS, INIFILE);
	w->hexagons.base = (int) GetPrivateProfileInt(SECTION,
		"base", DEFAULT_BASE, INIFILE);
	w->hexagons.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->hexagons.reverse = (BOOL) GetPrivateProfileInt(SECTION,
		"reverseVideo", DEFAULT_REVERSE, INIFILE);
	/* Tan (wheat4) */
	(void) GetPrivateProfileString(SECTION,
		"frameColor", "139 126 102", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->hexagons.frameGC = RGB(color.red, color.green, color.blue);
	/* gray75 */
	(void) GetPrivateProfileString(SECTION,
		"tileColor", "191 191 191", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->hexagons.tileGC[1] = RGB(color.red, color.green, color.blue);
	w->hexagons.tileGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->hexagons.tileGC[2] = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	/* gray25 */
	(void) GetPrivateProfileString(SECTION,
		"textColor", "64 64 64", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->hexagons.textGC = RGB(color.red, color.green, color.blue);
	/* #AEB2C3 */
	(void) GetPrivateProfileString(SECTION,
		"background", "174 178 195", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->hexagons.inverseGC[1] = RGB(color.red, color.green, color.blue);
	w->hexagons.inverseGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->hexagons.inverseGC[2] = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	w->hexagons.inverseGC[3] = RGB(darker(darker(color.red)),
		darker(darker(color.green)), darker(darker(color.blue)));
	w->hexagons.inverseGC[4] = RGB(darker(darker(darker(color.red))),
		darker(darker(darker(color.green))),
		darker(darker(darker(color.blue))));
	w->hexagons.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", 10, INIFILE);
	w->hexagons.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", FALSE, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"bumpSound", BUMPSOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->hexagons.bumpSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->hexagons.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->hexagons.scoreFile, szBuf, 80);
}

void
destroyPuzzle(HexagonsWidget w, HBRUSH brush)
{
	if (w->core.memDC) {
		DeleteObject(w->hexagons.bufferTiles);
		DeleteDC(w->core.memDC);
		w->core.memDC = NULL;
	}
	deleteMoves(&undo);
	deleteMoves(&redo);
	(void) DeleteObject(brush);
	PostQuitMessage(0);
}

#else
#define MAX_INTENSITY 0xFFFF

static Pixel
brighter(HexagonsWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = DefaultColormapOfScreen(XtScreen(w));
	int i = (int) ((1 - BRIGHT_FACTOR) * MAX_INTENSITY);

	color.pixel = pixel;
	VOID XQueryColor(XtDisplay(w), colormap, &color);
	if (color.red < i)
		color.red = i;
	if (color.green < i)
		color.green = i;
	if (color.blue < i)
		color.blue = i;
	color.red = (unsigned short) MIN(color.red / BRIGHT_FACTOR, MAX_INTENSITY);
	color.green = (unsigned short) MIN(color.green / BRIGHT_FACTOR, MAX_INTENSITY);
	color.blue = (unsigned short) MIN(color.blue / BRIGHT_FACTOR, MAX_INTENSITY);
	if (XAllocColor(XtDisplay(w), colormap, &color))
		return color.pixel;
	return pixel;
}

static Pixel
darker(HexagonsWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = DefaultColormapOfScreen(XtScreen(w));

	color.pixel = pixel;
	VOID XQueryColor(XtDisplay(w), colormap, &color);
	color.red = (unsigned short) (color.red * DARK_FACTOR);
	color.green = (unsigned short) (color.green * DARK_FACTOR);
	color.blue = (unsigned short) (color.blue * DARK_FACTOR);
	if (XAllocColor(XtDisplay(w), colormap, &color))
		return color.pixel;
	return pixel;
}

static void
setAllColors(HexagonsWidget w)
{
	Display *display = XtDisplay(w);
	Window window = XtWindow(w);
	XGCValues values;
	XtGCMask valueMask;

	valueMask = GCForeground | GCBackground;
	if (w->hexagons.reverse) {
		values.foreground = w->hexagons.foreground;
		values.background = w->hexagons.background;
	} else {
		values.foreground = w->hexagons.background;
		values.background = w->hexagons.foreground;
	}
	if (w->hexagons.inverseGC[1])
		XtReleaseGC((Widget) w, w->hexagons.inverseGC[1]);
	w->hexagons.inverseGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->hexagons.mono) {
		values.foreground = brighter(w, (w->hexagons.reverse) ?
			w->hexagons.foreground : w->hexagons.background);
	}
	if (w->hexagons.inverseGC[0])
		XtReleaseGC((Widget) w, w->hexagons.inverseGC[0]);
	w->hexagons.inverseGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->hexagons.mono) {
		values.foreground = darker(w, (w->hexagons.reverse) ?
			w->hexagons.foreground : w->hexagons.background);
	}
	if (w->hexagons.inverseGC[2])
		XtReleaseGC((Widget) w, w->hexagons.inverseGC[2]);
	w->hexagons.inverseGC[2] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->hexagons.mono) {
		values.foreground = darker(w, darker(w, (w->hexagons.reverse) ?
			w->hexagons.foreground : w->hexagons.background));
	}
	if (w->hexagons.inverseGC[3])
		XtReleaseGC((Widget) w, w->hexagons.inverseGC[3]);
	w->hexagons.inverseGC[3] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->hexagons.mono) {
		values.foreground = darker(w, darker(w, darker(w,
			(w->hexagons.reverse) ? w->hexagons.foreground :
			w->hexagons.background)));
	}
	if (w->hexagons.inverseGC[4])
		XtReleaseGC((Widget) w, w->hexagons.inverseGC[4]);
	w->hexagons.inverseGC[4] = XtGetGC((Widget) w, valueMask, &values);
	if (w->hexagons.mono) {
		if (w->hexagons.reverse) {
			values.foreground = w->hexagons.background;
			values.background = w->hexagons.foreground;
		} else {
			values.foreground = w->hexagons.foreground;
			values.background = w->hexagons.background;
		}
	} else {
		values.foreground = w->hexagons.frameColor;
		values.background = w->hexagons.foreground;
	}
	if (w->hexagons.frameGC)
		XtReleaseGC((Widget) w, w->hexagons.frameGC);
	w->hexagons.frameGC = XtGetGC((Widget) w, valueMask, &values);
	w->hexagons.grainLR = XCreateBitmapFromData(display, window,
		(char *) grain_lr_bits, grain_lr_width, grain_lr_height);
	w->hexagons.grainTLBR = XCreateBitmapFromData(display, window,
		(char *) grain_tlbr_bits, grain_tlbr_width, grain_tlbr_height);
	w->hexagons.grainTRBL = XCreateBitmapFromData(display, window,
		(char *) grain_trbl_bits, grain_trbl_width, grain_trbl_height);
	if (w->hexagons.mono) {
		if (w->hexagons.reverse) {
			values.foreground = w->hexagons.background;
			values.background = w->hexagons.foreground;
		} else {
			values.foreground = w->hexagons.foreground;
			values.background = w->hexagons.background;
		}
	} else {
		values.foreground = w->hexagons.tileColor;
		values.background = w->hexagons.textColor;
	}
	if (w->hexagons.tileGC[1])
		XtReleaseGC((Widget) w, w->hexagons.tileGC[1]);
	w->hexagons.tileGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->hexagons.mono) {
		values.foreground = brighter(w, w->hexagons.tileColor);
	}
	if (w->hexagons.tileGC[0])
		XtReleaseGC((Widget) w, w->hexagons.tileGC[0]);
	w->hexagons.tileGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->hexagons.mono) {
		values.foreground = darker(w, w->hexagons.tileColor);
	}
	if (w->hexagons.tileGC[2])
		XtReleaseGC((Widget) w, w->hexagons.tileGC[2]);
	w->hexagons.tileGC[2] = XtGetGC((Widget) w, valueMask, &values);
	if (w->hexagons.mono) {
		if (w->hexagons.reverse) {
			values.foreground = w->hexagons.foreground;
			values.background = w->hexagons.background;
		} else {
			values.foreground = w->hexagons.background;
			values.background = w->hexagons.foreground;
		}
	} else {
		values.foreground = w->hexagons.textColor;
		values.background = w->hexagons.tileColor;
	}
	if (w->hexagons.textGC)
		XtReleaseGC((Widget) w, w->hexagons.textGC);
	w->hexagons.textGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->hexagons.fontInfo)
		XSetFont(XtDisplay(w), w->hexagons.textGC,
			w->hexagons.fontInfo->fid);
}

static Boolean
setValuesPuzzle(Widget current, Widget request, Widget renew)
{
	HexagonsWidget c = (HexagonsWidget) current, w = (HexagonsWidget) renew;
	Boolean redraw = FALSE;
	Boolean redrawTiles = FALSE;

	checkTiles(w);
	if (w->hexagons.font != c->hexagons.font ||
			w->hexagons.textColor != c->hexagons.textColor ||
			w->hexagons.reverse != c->hexagons.reverse ||
			w->hexagons.mono != c->hexagons.mono) {
		loadFont(w);
		setAllColors(w);
		redrawTiles = True;
	} else if (w->hexagons.background != c->hexagons.background ||
			w->hexagons.foreground != c->hexagons.foreground ||
			w->hexagons.tileColor != c->hexagons.tileColor) {
		setAllColors(w);
		redrawTiles = TRUE;
	}
	if (w->hexagons.sizeX != c->hexagons.sizeX ||
			w->hexagons.sizeY != c->hexagons.sizeY ||
			w->hexagons.corners != c->hexagons.corners ||
			w->hexagons.base != c->hexagons.base) {
		sizePuzzle(w);
		redraw = TRUE;
	} else if (w->hexagons.offset.x != c->hexagons.offset.x ||
			w->hexagons.offset.y != c->hexagons.offset.y) {
		resizePuzzle(w);
		redraw = TRUE;
	}
	if (w->hexagons.menu != ACTION_IGNORE) {
		int menu = w->hexagons.menu;

		w->hexagons.menu = ACTION_IGNORE;
		switch (menu) {
		case ACTION_GET:
			getTiles(w);
			break;
		case ACTION_WRITE:
			writeTiles(w);
			break;
		case ACTION_UNDO:
			undoTiles(w);
			break;
		case ACTION_REDO:
			redoTiles(w);
			break;
		case ACTION_CLEAR:
			clearTiles(w);
			break;
		case ACTION_RANDOMIZE:
			randomizeTiles(w);
			break;
		case ACTION_SOLVE:
			solveTiles(w);
			break;
		case ACTION_CORNERS:
			cornersTiles(w);
			break;
		case ACTION_SPEED:
			speedTiles(w);
			break;
		case ACTION_SLOW:
			slowTiles(w);
			break;
		case ACTION_SOUND:
			soundTiles(w);
			break;
		default:
			break;
		}
	}
	if (redrawTiles && !redraw && XtIsRealized(renew) && renew->core.visible) {
		eraseFrame(c, 0);
		drawFrame(w, 0, True, w->hexagons.focus);
		drawAllTiles(w);
	}
	return (redraw);
}

static void
destroyPuzzle(Widget old)
{
	HexagonsWidget w = (HexagonsWidget) old;
	Display *display = XtDisplay(w);
	int i;

#if defined( USE_SOUND ) && defined( USE_ESOUND )
	(void) shutdown_sound();
#endif
	XtReleaseGC(old, w->hexagons.textGC);
	for (i = 0; i < FG_SHADES; i++)
		XtReleaseGC(old, w->hexagons.tileGC[i]);
	for (i = 0; i < BG_SHADES; i++)
		XtReleaseGC(old, w->hexagons.inverseGC[i]);
	XtRemoveCallbacks(old, XtNselectCallback, w->hexagons.select);
	if (w->hexagons.colormap != None) {
		XInstallColormap(display, w->hexagons.oldColormap);
		XFreeColormap(display, w->hexagons.colormap);
	}
	for (i = 0; i < 2; i++)
		if (w->hexagons.bufferTiles[i] != None)
			XFreePixmap(display, w->hexagons.bufferTiles[i]);
	if (w->hexagons.fontInfo) {
		XUnloadFont(display, w->hexagons.fontInfo->fid);
		XFreeFont(display, w->hexagons.fontInfo);
	}
	deleteMoves(&undo);
	deleteMoves(&redo);
}

static void
quitPuzzle(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	XtCloseDisplay(XtDisplay(w));
	exit(0);
}
#endif

static void
resizeTiles(HexagonsWidget w)
{
	int i;

#ifdef WINVER
	if (w->core.memDC == NULL) {
		w->core.memDC = CreateCompatibleDC(w->core.hDC);
		if (w->core.memDC == NULL) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	for (i = 0; i < 2; i++) {
		if (w->hexagons.bufferTiles[i] != NULL) {
			DeleteObject(w->hexagons.bufferTiles[i]);
				w->hexagons.bufferTiles[i] = NULL;
		}
		if (!hasValue(w->hexagons.picture)) {
			if ((w->hexagons.bufferTiles[i] =
					CreateCompatibleBitmap(w->core.hDC,
					w->core.width,
					w->core.height)) == NULL) {
				DISPLAY_ERROR("Not enough memory, exiting.");
			}
		}
	}
#else
	Display *display = XtDisplay(w);
	Window window = XtWindow(w);
	XWindowAttributes xgwa;

	(void) XGetWindowAttributes(display, window, &xgwa);
	if (w->hexagons.oldColormap == None) {
		w->hexagons.mono = (xgwa.depth < 2 || w->hexagons.mono);
		w->hexagons.oldColormap = xgwa.colormap;
	}
	for (i = 0; i < 2; i++) {
		if (w->hexagons.bufferTiles[i] != None) {
			XFreePixmap(display, w->hexagons.bufferTiles[i]);
			w->hexagons.bufferTiles[i] = None;
		}
		if ((w->hexagons.bufferTiles[i] = XCreatePixmap(display,
				window, w->core.width, w->core.height,
				xgwa.depth)) == None) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
#endif
	if (hasValue(w->hexagons.picture)) {
#ifdef WINVER
		for (i = 0; i < 2; i++) {
			w->hexagons.bufferTiles[i] =
				LoadBitmap(w->core.hInstance,
				w->hexagons.picture);
		}
#else
		if (w->hexagons.image != NULL) {
			destroyImage(&(w->hexagons.image),
				&(w->hexagons.graphicsFormat));
		}
		if (!getImage(display, window,
				xgwa.visual, w->hexagons.oldColormap, xgwa.depth,
				&(w->hexagons.image), w->hexagons.picture,
				w->hexagons.install, &(w->hexagons.graphicsFormat),
				&(w->hexagons.colormap))) {
			w->hexagons.picture = NULL;
		} else if (w->hexagons.image == NULL) {
			w->hexagons.picture = NULL;
		}
#endif
	}
#ifndef WINVER
	if (!hasValue(w->hexagons.picture) &&
			!fixedColors(xgwa.visual, xgwa.depth, w->hexagons.install) &&
			w->hexagons.colormap == None) {
		w->hexagons.colormap = XCreateColormap(display, window,
			xgwa.visual, AllocNone);
	}
	setAllColors(w);
	for (i = 0; i < 2; i++) {
		FILLRECTANGLE(w, w->hexagons.bufferTiles[i],
			w->hexagons.inverseGC[2],
			0, 0, w->core.width, w->core.height);
		if (hasValue(w->hexagons.picture)) {
			(void) XPutImage(display, w->hexagons.bufferTiles[i],
				w->hexagons.inverseGC[2], w->hexagons.image, 0, 0,
				0, 0,
				MIN(w->hexagons.image->width, w->core.width),
				MIN(w->hexagons.image->height, w->core.height));
		}
	}
#endif
	for (i = 0; i <= 6; i++) {
		hexagonList[NOCORN][i].x = w->hexagons.tileSize.x *
			hexagonUnit[NOCORN][i].x / 4;
		hexagonList[NOCORN][i].y = 3 * w->hexagons.tileSize.y *
			hexagonUnit[NOCORN][i].y / 4;
		hexagonList[CORNERS][i].x = w->hexagons.tileSize.x *
			hexagonUnit[CORNERS][i].x / 2;
		hexagonList[CORNERS][i].y = w->hexagons.tileSize.y *
			hexagonUnit[CORNERS][i].y / 2;
	}
	if (!hasValue(w->hexagons.picture)) {
		drawAllBufferedTiles(w);
	}
}

#ifndef WINVER
static
#endif
void
resizePuzzle(HexagonsWidget w)
{
	Point tempSize;
#ifdef WINVER
	RECT rect;

	/* Determine size of client area */
	(void) GetClientRect(w->core.hWnd, &rect);
	w->core.width = rect.right;
	w->core.height = rect.bottom;
#endif

	w->hexagons.frameThickness.y = MIN(16,
		MIN(w->core.width, w->core.height) / 16);
	w->hexagons.frameThickness.x = (int) (sqrt_3 * w->hexagons.frameThickness.y);
	w->hexagons.delta.x = 2;
	w->hexagons.delta.y = 2;
	w->hexagons.tileSize.x = MAX((2 * ((int) w->core.width - 2 * w->hexagons.frameThickness.x +
			2 * w->hexagons.delta.x - 1) - 2 * (w->hexagons.sizeX +
			w->hexagons.sizeY) * w->hexagons.delta.x) / (2 *
			(w->hexagons.sizeX + w->hexagons.sizeY) - 1), 0);
	w->hexagons.tileSize.y = MAX(((int) w->core.height - 2 * w->hexagons.frameThickness.y - 1 -
				2 * w->hexagons.sizeY * w->hexagons.delta.y) /
				(3 * w->hexagons.sizeY - 1), 0);
	w->hexagons.offset.x = w->hexagons.tileSize.x + w->hexagons.delta.x;
	w->hexagons.offset.y = w->hexagons.tileSize.y + 2 * w->hexagons.delta.y;
	tempSize.y = (int) (w->hexagons.offset.x / sqrt_3);
	tempSize.x = (int) (w->hexagons.offset.y * sqrt_3);
	if (tempSize.y <= w->hexagons.offset.y) {
		w->hexagons.offset.x = w->hexagons.tileSize.x + w->hexagons.delta.x;
		w->hexagons.offset.y = tempSize.y;
	} else {		/* tempSize.x <= w->hexagons.offset.x */
		w->hexagons.offset.x = tempSize.x;
		w->hexagons.offset.y = w->hexagons.tileSize.y + 2 * w->hexagons.delta.y;
	}
	w->hexagons.tileSize.x = MAX(w->hexagons.offset.x - 1, 0);
	w->hexagons.tileSize.y = MAX(w->hexagons.offset.y - 2 * w->hexagons.delta.y + 1,
		0);
	w->hexagons.puzzleSize.x = (w->hexagons.sizeX + w->hexagons.sizeY) *
		w->hexagons.offset.x - w->hexagons.tileSize.x / 2 -
		2 * w->hexagons.delta.x + 1;
	w->hexagons.puzzleSize.y = w->hexagons.sizeY * (3 *
		w->hexagons.tileSize.y + 2 * w->hexagons.delta.y) -
		w->hexagons.tileSize.y + 2;
	w->hexagons.puzzleOffset.x =
		((int) w->core.width - w->hexagons.puzzleSize.x + 2) / 2;
	w->hexagons.puzzleOffset.y =
		((int) w->core.height - w->hexagons.puzzleSize.y + 4) / 2;
}

#ifndef WINVER
static
#endif
void
sizePuzzle(HexagonsWidget w)
{
	resetTiles(w);
	resizePuzzle(w);
}

#ifndef WINVER
static
#endif
void
initializePuzzle(
#ifdef WINVER
HexagonsWidget w, HBRUSH brush
#else
Widget request, Widget renew
#endif
)
{
#ifdef WINVER
	setValuesPuzzle(w);
	brush = CreateSolidBrush(w->hexagons.inverseGC[2]);
	SETBACK(w->core.hWnd, brush);
	(void) SRAND(time(NULL));
	w->hexagons.bufferTiles[0] = NULL;
	w->hexagons.bufferTiles[1] = NULL;
#else
	HexagonsWidget w = (HexagonsWidget) renew;
	int i;

	(void) SRAND(getpid());
	w->hexagons.bufferTiles[0] = None;
	w->hexagons.bufferTiles[1] = None;
	w->hexagons.colormap = None;
	w->hexagons.oldColormap = None;
	w->hexagons.fontInfo = NULL;
	w->hexagons.textGC = NULL;
	for (i = 0; i < FG_SHADES; i++)
		w->hexagons.tileGC[i] = NULL;
	for (i = 0; i < BG_SHADES; i++)
		w->hexagons.inverseGC[i] = NULL;
	w->hexagons.image = NULL;
#endif
	w->hexagons.focus = False;
	loadFont(w);
	w->hexagons.tileOfPosition = NULL;
	checkTiles(w);
	newMoves(&undo);
	newMoves(&redo);;
	w->hexagons.numSlices = ((w->hexagons.delay < MAX_SLICES) ?
		w->hexagons.delay + 1 : MAX_SLICES);
	w->hexagons.cheat = False;
	sizePuzzle(w);
#ifdef USE_SOUND
#ifdef USE_NAS
	dsp = XtDisplay(w);
#endif
#ifdef USE_ESOUND
	(void) init_sound();
#endif
#endif
}

#ifndef WINVER
static
#endif
void
exposePuzzle(
#ifdef WINVER
HexagonsWidget w
#else
Widget renew, XEvent *event, Region region
#endif
)
{
#ifndef WINVER
	HexagonsWidget w = (HexagonsWidget) renew;

	if (!w->core.visible)
		return;
#endif
	resizeTiles(w);
	eraseFrame(w, 0);
	drawFrame(w, 0, True, w->hexagons.focus);
	drawAllTiles(w);
}

#ifndef WINVER
static
#endif
void
hidePuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	setPuzzle(w, ACTION_HIDE);
}

#ifndef WINVER
static
#endif
void
selectPuzzle(HexagonsWidget w
#ifdef WINVER
, const int x, const int y
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	int row, rowType;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
#endif

	w->hexagons.currentPosition = positionToTile(w, x, y, &row);
	if (w->hexagons.currentPosition >= 0) {
		if (checkSolved(w)) {
			moveNoTiles(w);
			w->hexagons.currentPosition = -1;
			return;
		}
		w->hexagons.currentRow[ROW] = row;
		w->hexagons.currentRow[TRBL] = toTrBl(w, w->hexagons.currentPosition, row);
		w->hexagons.currentRow[TLBR] = toTlBr(w, w->hexagons.currentPosition, row);
		if (w->hexagons.corners)
			rowType = movableCornerTile(w);
		else
			rowType = movableNoCornTile(w);
		if (rowType < 0) {
			drawTile(w, w->hexagons.currentPosition,
				rowType == SPACE, False, TRUE, 0, 0);
			FLUSH(w);
			Sleep(100);
			drawTile(w, w->hexagons.currentPosition,
				True, True, TRUE, 0, 0);
			if (rowType != SPACE)
				drawTile(w, w->hexagons.currentPosition,
					False, False, FALSE, 0, 0);
			setPuzzle(w, rowType);
			w->hexagons.currentPosition = -1;
			return;
		}
		drawTile(w, w->hexagons.currentPosition,
			False, False, TRUE, 0, 0);
	} else
		w->hexagons.currentPosition = -1;
}

#ifndef WINVER
static
#endif
void
releasePuzzle(HexagonsWidget w
#ifdef WINVER
, const int x, const int y
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	int pos, row, space;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
#endif

	if (w->hexagons.currentPosition < 0)
		return;
	drawTile(w, w->hexagons.currentPosition, True, True, TRUE, 0, 0);
	drawTile(w, w->hexagons.currentPosition, False, False, FALSE, 0, 0);
	if (!w->hexagons.corners) {
		selectNoCornTiles(w);
		w->hexagons.currentPosition = -1;
		return;
	}
	pos = positionToTile(w, x, y, &row);
	if (pos >= 0) {
		if (w->hexagons.spacePosition[HIGH] == pos)
			space = (w->hexagons.spacePosition[HIGH] >
				w->hexagons.spacePosition[LOW]);
		else if (w->hexagons.spacePosition[LOW] == pos)
			space = (w->hexagons.spacePosition[HIGH] <
				w->hexagons.spacePosition[LOW]);
		else {
			w->hexagons.currentPosition = -1;
			return;
		}
		selectCornerTiles(w, space);
	}
	w->hexagons.currentPosition = -1;
}

#ifndef WINVER
static void
clearWithQueryPuzzle(HexagonsWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->hexagons.started)
		clearTiles(w);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	else {
		setPuzzle(w, ACTION_CLEAR_QUERY);
	}
#endif
}

static void
clearWithDoubleClickPuzzle(HexagonsWidget w
, XEvent *event, char **args, int nArgs
)
{
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	if (!w->hexagons.started)
#endif
		clearTiles(w);
}
#endif

#ifndef WINVER
static
#endif
void
getPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	getTiles(w);
}

#ifndef WINVER
static
#endif
void
writePuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	writeTiles(w);
}

#ifndef WINVER
static
#endif
void
undoPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	undoTiles(w);
}

#ifndef WINVER
static
#endif
void
redoPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	redoTiles(w);
}

#ifndef WINVER
static
#endif
void
clearPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	clearTiles(w);
}

#ifndef WINVER
static
#endif
void
randomizePuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	randomizeTiles(w);
}

#ifndef WINVER
static
#endif
void
solvePuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	solveTiles(w);
}

#ifndef WINVER
static
#endif
void
cornersPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	cornersTiles(w);
}

#ifndef WINVER
static
#endif
void
speedUpPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	speedTiles(w);
}

#ifndef WINVER
static
#endif
void
slowDownPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	slowTiles(w);
}

#ifndef WINVER
static
#endif
void
toggleSoundPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	soundTiles(w);
}

#ifndef WINVER
static
#endif
void
enterPuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->hexagons.focus = True;
	drawFrame(w, 0, False, w->hexagons.focus);
}

#ifndef WINVER
static
#endif
void
leavePuzzle(HexagonsWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->hexagons.focus = False;
	drawFrame(w, 0, False, w->hexagons.focus);
}

#ifndef WINVER
static void
movePuzzleTl(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, TL, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleTop(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, TOP, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleTr(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, TR, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleLeft(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, LEFT, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleRight(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, RIGHT, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBl(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, BL, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBottom(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, BOTTOM, (int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBr(HexagonsWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, BR, (int) (event->xkey.state & ControlMask));
}
#endif
