/*
 * @(#)Triangles.c
 *
 * Copyright 1994 - 2024  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 Triangles */

#include "file.h"
#include "rngs.h"
#include "sound.h"
#include "TrianglesP.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
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "wtriangles.ini"
#endif

#define SECTION "setup"
#else
#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(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void destroyPuzzle(Widget old);
static void resizePuzzle(TrianglesWidget w);
static void sizePuzzle(TrianglesWidget w);
static void initializePuzzle(Widget request, Widget renew);
static void exposePuzzle(Widget renew, XEvent *event, Region region);
static void hidePuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void selectPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void releasePuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void clearWithQueryPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void clearWithDoubleClickPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void getPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void writePuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void undoPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void redoPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void clearPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void solvePuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void cornersPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void speedUpPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void slowDownPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void toggleSoundPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void enterPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void leavePuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTl(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTr(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTop(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBl(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBr(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(TrianglesWidget 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>Prior: MoveTr()\n\
 <KeyPress>KP_9: MoveTr()\n\
 <KeyPress>R9: MoveTr()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\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>Next: MoveBr()\n\
 <KeyPress>KP_3: MoveBr()\n\
 <KeyPress>R15: MoveBr()\n\
 <KeyPress>KP_2: MoveBottom()\n\
 <KeyPress>R14: MoveBottom()\n\
 <Btn1Down>: Select()\n\
 <Btn1Up>: Release()\n\
 <Btn3Down>: ClearMaybe()\n\
 <Btn3Down>(2+): Clear2()\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 *) "MoveTr", (XtActionProc) movePuzzleTr},
	{(char *) "MoveTop", (XtActionProc) movePuzzleTop},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBl", (XtActionProc) movePuzzleBl},
	{(char *) "MoveBr", (XtActionProc) movePuzzleBr},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleBottom},
	{(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(TrianglesWidget, core.width),
	 XtRString, (caddr_t) "300"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(TrianglesWidget, core.height),
	 XtRString, (caddr_t) "245"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(TrianglesWidget, triangles.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(TrianglesWidget, triangles.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(TrianglesWidget, triangles.frameColor),
	 XtRString, (caddr_t) "wheat4" /*XtDefaultForeground*/},
	{XtNtileColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(TrianglesWidget, triangles.tileColor),
	 XtRString, (caddr_t) "gray75" /*XtDefaultForeground*/},
	{XtNtextColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(TrianglesWidget, triangles.textColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultBackground*/},
	{XtNstippleFrame, XtCStippleFrame, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.stippleFrame),
	 XtRString, (caddr_t) "TRUE"},
	{XtNinstall, XtCInstall, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.install),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpicture, XtCPicture, XtRString, sizeof (String),
	 XtOffset(TrianglesWidget, triangles.picture),
	 XtRString, (caddr_t) PICTURE},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(TrianglesWidget, triangles.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNbumpSound, XtCBumpSound, XtRString, sizeof (String),
	 XtOffset(TrianglesWidget, triangles.bumpSound),
	 XtRString, (caddr_t) BUMPSOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(TrianglesWidget, triangles.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNsizeX, XtCSizeX, XtRInt, sizeof (int),
	 XtOffset(TrianglesWidget, triangles.sizeX),
	 XtRString, (caddr_t) "1"}, /* DEFAULT_TILESX */
	{XtNsizeY, XtCSizeY, XtRInt, sizeof (int),
	 XtOffset(TrianglesWidget, triangles.sizeY),
	 XtRString, (caddr_t) "4"}, /* DEFAULT_TILESY */
	{XtNcorners, XtCCorners, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.corners),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_CORNERS */
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(TrianglesWidget, triangles.base),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_BASE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(TrianglesWidget, triangles.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(TrianglesWidget, triangles.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(TrianglesWidget, triangles.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(TrianglesWidget, triangles.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(TrianglesWidget, triangles.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(TrianglesWidget, triangles.select),
	 XtRCallback, (caddr_t) NULL}
};

TrianglesClassRec trianglesClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Triangles",	/* class name */
		sizeof (TrianglesRec),	/* 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 trianglesWidgetClass = (WidgetClass) & trianglesClassRec;

void
setPuzzle(TrianglesWidget w, int reason)
{
	trianglesCallbackStruct cb;

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

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

	if (w->triangles.fontInfo) {
		XUnloadFont(XtDisplay(w), w->triangles.fontInfo->fid);
		XFreeFont(XtDisplay(w), w->triangles.fontInfo);
	}
	if (w->triangles.font && (w->triangles.fontInfo =
			XLoadQueryFont(display, w->triangles.font)) == NULL) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 512,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->triangles.font, altfontname);
#else
		(void) sprintf(buf,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->triangles.font, altfontname);
#endif
		DISPLAY_WARNING(buf);
		if ((w->triangles.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->triangles.fontInfo) {
		w->triangles.digitOffset.x = XTextWidth(w->triangles.fontInfo, "8", 1)
			/ 2;
		w->triangles.digitOffset.y = w->triangles.fontInfo->max_bounds.ascent
			/ 2;
	} else
#endif
	{
		w->triangles.digitOffset.x = 3;
		w->triangles.digitOffset.y = 4;
	}
}

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

static const double sqrt_3 = 1.73205080756887729352744634150587237;
static Point triangleUnit[MAX_ORIENT][ROW_TYPES + 1] =
{
	{
		{0, 0},
		{-1, -1},
		{2, 0},
		{-1, 1}
	},
	{
		{0, 0},
		{1, 1},
		{-2, 0},
		{1, -1}
	}
};
static Point triangleList[MAX_ORIENT][ROW_TYPES + 1];
static Point hexagonUnit[7] =
{
	{1, -1},
	{1, 1},
	{-1, 1},
	{-2, 0},
	{-1, -1},
	{1, -1},
	{2, 0}
};
static Point hexagonList[7];
#ifdef RHOMBUS
/* Needs more work if used */
static Point rhombusUnit[ROW_TYPES][ROW_TYPES + 2] =
{
	{
		{0, 0},
		{-2, 0},
		{1, 1},
		{2, 0},
		{-1, -1}
	},
	{
		{0, 0},
		{-1, 1},
		{2, 0},
		{1, -1},
		{-2, -0}
	},
	{
		{0, 0},
		{-1, 1},
		{1, 1},
		{1, -1},
		{-1, -1}
	}
};
static Point rhombusList[ROW_TYPES][ROW_TYPES + 2];
#endif

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

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

	if (w->triangles.sizeX < MIN_TILES) {
		intCat(&buf1,
			"Number of down triangles 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->triangles.sizeX = DEFAULT_TILESX;
	}
	if (w->triangles.sizeY < MIN_TILES) {
		intCat(&buf1,
			"Number of down triangles 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->triangles.sizeY = DEFAULT_TILESY;
	}
	if (w->triangles.delay < 0) {
		intCat(&buf1, "Delay cannot be negative (",
			w->triangles.delay);
		stringCat(&buf2, buf1, "), taking absolute value");
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		w->triangles.delay = -w->triangles.delay;
	}
	if (w->triangles.base < MIN_BASE || w->triangles.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->triangles.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 int
fromRow(TrianglesWidget w, const int row)
{
	return (w->triangles.sizeX + row) * (w->triangles.sizeX + row)
		- (w->triangles.sizeX - 1) * (w->triangles.sizeX - 1);
}

static int
positionFromRow(TrianglesWidget w, int rowPosition, int posRow)
{
	return rowPosition + fromRow(w, posRow);
}


static int
positionInRow(TrianglesWidget w, int row, int trbl, int tlbr)
{
	return w->triangles.sizeX + trbl - tlbr + row - 1;
}

static int
toPosition(TrianglesWidget w, int row, int trbl, int tlbr)
{
	return positionFromRow(w, positionInRow(w, row, trbl, tlbr), row - 1);
}

static int
toOrient(TrianglesWidget w, int row, int trbl, int tlbr)
{
	return ((positionInRow(w, row, trbl, tlbr) % 2 == 1) ? DOWN : UP);
}

int
toRow(TrianglesWidget w, const int pos)
{
	return SQRT(pos + (w->triangles.sizeX - 1) * (w->triangles.sizeX - 1))
		- w->triangles.sizeX + 1;
}

/* Passing row so there is no sqrt calculation again */
int
toTrBl(TrianglesWidget w, const int pos, const int posRow)
{
	return (pos - fromRow(w, posRow - 1)) >> 1;
}

int
toTlBr(TrianglesWidget w, const int pos, int const posRow)
{
	return (fromRow(w, posRow) - pos - 1) >> 1;
}

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

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(TrianglesWidget w, int pos, int row)
{
	return (pos - fromRow(w, row - 1) - row + w->triangles.sizeY)
		* w->triangles.offset.x / 2
		+ w->triangles.delta.x / 2 + w->triangles.puzzleOffset.x;
}

static int
cartesianY(TrianglesWidget w, int row, int orient)
{
	return ((orient == UP) ? 0 : w->triangles.tileSize.y)
		+ row * w->triangles.offset.y + w->triangles.delta.y + 2
		+ w->triangles.puzzleOffset.y;
}

static int
tileNFromSpace(TrianglesWidget w, int n, int rowType, int orient, int direction)
{
	int pos = w->triangles.spacePosition[orient];

	if (rowType == ROW) /* This one is easy */
		return (pos + ((direction == UP) ? -n : n));
	else {
		int row = toRow(w, pos);
		int trbl = toTrBl(w, pos, row);
		int tlbr = toTlBr(w, pos, row);
		int offset1, offset2;

		if (direction == UP) {
			offset1 = -((n >> 1) + (n & 1));
			offset2 = -(n >> 1);
		} else {
			offset1 = n >> 1;
			offset2 = (n >> 1) + (n & 1);
		}
		if (rowType == TRBL)
			return ((orient == DOWN) ?
				toPosition(w, row + offset1, trbl, tlbr + offset2) :
				toPosition(w, row + offset2, trbl, tlbr + offset1));
		else /*if (rowType == TLBR)*/
			return ((orient == DOWN) ?
				toPosition(w, row + offset1, trbl + offset2, tlbr) :
				toPosition(w, row + offset2, trbl + offset1, tlbr));
	}
}

static int
tile1From(TrianglesWidget w, int pos, int rowType)
{
	int row = toRow(w, pos);
	int trbl = toTrBl(w, pos, row);
	int tlbr = toTlBr(w, pos, row);
	int orient = toOrient(w, row, trbl, tlbr);

	if (orient == UP)
		switch(rowType) {
		case TR:
			if (row != w->triangles.sizeY - 1)
				return toPosition(w, row + 1, trbl, tlbr);
			else
				return -1;
		case RIGHT:
			if (trbl != 0)
				return pos - 1;
			else
				return -1;
		case BR:
			if (trbl != 0)
				return pos - 1;
			else
				return -1;
		case BL:
			if (tlbr != 0)
				return pos + 1;
			else
				return -1;
		case LEFT:
			if (tlbr != 0)
				return pos + 1;
			else
				return -1;
		case TL:
			if (row != w->triangles.sizeY - 1)
				return toPosition(w, row + 1, trbl, tlbr);
			else
				return -1;
		default:
			return -1;
		}
	else
		switch(rowType) {
		case TR:
		case RIGHT:
			return pos - 1;
		case BR:
		case BL:
			return toPosition(w, row - 1, trbl, tlbr);
		case LEFT:
		case TL:
			return pos + 1;
		default:
			return -1;
		}
}

static int
nextNoCornDir(TrianglesWidget w, int pos) {
	int row = toRow(w, pos);
	int trbl, tlbr;

	if (w->triangles.spaceRow[UP][ROW] == row) {
		if (w->triangles.spacePosition[UP] == pos - 1)
			return LEFT;
		else if (w->triangles.spacePosition[UP] == pos + 1)
			return RIGHT;
		else
			return -1;
	}
	trbl = toTrBl(w, pos, row);
	tlbr = toTlBr(w, pos, row);
	if (w->triangles.spaceRow[UP][TRBL] == trbl &&
			w->triangles.spaceRow[UP][TLBR] == tlbr) {
		if (w->triangles.spaceRow[UP][ROW] == row - 1)
			return TL; /* or TR */
		else if (w->triangles.spaceRow[UP][ROW] == row + 1)
			return BL; /* or BR */
		else
			return -1;
	}
	return -1;
}

Boolean
checkSolved(const TrianglesWidget w)
{
	int i;

	for (i = 1; i < w->triangles.sizeSize -
			((w->triangles.corners) ? CORNERS : NOCORN); i++)
		if (w->triangles.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(TrianglesWidget 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
fill3DTriangle(TrianglesWidget w, Pixmap dr, GC gc, GC darkerGC, GC brighterGC,
	Point * list, Boolean raised, Boolean orient)
{
	GC currentGC = (raised) ? gc : darkerGC;
	Point tempList[7];
	int i;

	for (i = 0; i < 3; 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;
	}
	tempList[3].x = tempList[0].x;
	tempList[3].y = tempList[1].y;

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

static void
fill3DHexagon(TrianglesWidget w, Pixmap dr, GC gc, GC darkerGC, GC brighterGC,
	Point * list, Boolean raised)
{
	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);
	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);
	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 - 1, tempList[0].y,
		tempList[1].x - 1, tempList[1].y);
	DRAWLINE(w, dr, currentGC,
		tempList[0].x - 1, tempList[0].y + 1,
		tempList[1].x - 1, tempList[1].y);
	/*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);
	/*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
drawTriangleShadow(TrianglesWidget w, Pixmap dr, GC gc, int startX, int startY,
	Point * list, Boolean orient)
{
	Point tempList[3];
	int i;

	for (i = 0; i < 3; 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;
	}
	if (orient) {
		DRAWLINE(w, dr, gc,
			tempList[2].x, tempList[2].y,
			tempList[0].x, tempList[0].y);
		DRAWLINE(w, dr, gc,
			tempList[2].x + 1, tempList[2].y,
			tempList[0].x, tempList[0].y + 1);
	} else {
		DRAWLINE(w, dr, gc,
			tempList[0].x, tempList[0].y,
			tempList[1].x, tempList[1].y);
		DRAWLINE(w, dr, gc,
			tempList[1].x, tempList[1].y,
			tempList[2].x, tempList[2].y);
	}
}

static void
drawHexagonShadow(TrianglesWidget w, Pixmap dr, GC gc, int startX, int startY,
	Point * list)
{
	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);
	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(TrianglesWidget w, int pos, int orient, Boolean blank, Boolean erase,
		int pressedOffset, int offsetX, int offsetY)
{
	Pixmap *dr;
	Pixmap adr = 0;
	int dx, dy, row;
	int corners = (w->triangles.corners) ? 1 : 0;
	GC tileGC, textGC;

	/*dr = &(w->triangles.bufferTiles[pressedOffset]);*/
	dr = &adr;
	if (erase) {
		tileGC = w->triangles.inverseGC[2];
		textGC = w->triangles.inverseGC[2];
	} else {
		tileGC = w->triangles.tileGC[1];
		textGC = w->triangles.textGC;
	}
	row = toRow(w, pos);
	dx = cartesianX(w, pos, row) + offsetX + pressedOffset;
	dy = cartesianY(w, row, orient) + offsetY + pressedOffset;
	triangleList[orient][0].x = dx;
	triangleList[orient][0].y = dy;
	if (!corners) {
		hexagonList[0].x = dx + w->triangles.offset.x / 6;
		hexagonList[0].y = dy + ((orient == UP) ? 0 :
			-4 * w->triangles.tileSize.y / 3 - 1) +
			(w->triangles.tileSize.y + w->triangles.delta.y) / 3;
	}
	if (blank) {
		if (w->triangles.corners) {
			POLYGON(w, *dr, tileGC, textGC, triangleList[orient], 3,
				True, False);
			fill3DTriangle(w, *dr, tileGC, tileGC,
				textGC, triangleList[orient],
				pressedOffset == 0, orient == UP);
		} else {
			POLYGON(w, *dr, tileGC, textGC, hexagonList, 6,
				True, False);
			fill3DHexagon(w, *dr, tileGC, tileGC,
				tileGC, hexagonList,
				pressedOffset == 0);
		}
	} else {
		if (pressedOffset != 0) {
			if (w->triangles.corners)
				drawTriangleShadow(w, *dr, w->triangles.tileGC[2],
					-pressedOffset, -pressedOffset,
					triangleList[orient], orient == UP);
			else
				drawHexagonShadow(w, *dr, w->triangles.tileGC[2],
					-pressedOffset, -pressedOffset,
					hexagonList);
		}
#if 0
#include "mandrill.xbm"
		{
			/* 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->triangles.bufferTiles[0]);*/ /*BadMatch, 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
		if (w->triangles.corners) {
			fill3DTriangle(w, *dr, tileGC, w->triangles.tileGC[2],
				w->triangles.tileGC[0],
				triangleList[orient],
				pressedOffset == 0, orient == UP);
		} else
			fill3DHexagon(w, *dr, tileGC, w->triangles.tileGC[2],
				w->triangles.tileGC[0], hexagonList,
				pressedOffset == 0);
	}
	if (!blank) {
		int i = 0, digitOffsetX = 0, digitOffsetY = 0;
		int tile = w->triangles.tileOfPosition[pos];
		char buf[65];

		(void) int2String(w, buf, tile, w->triangles.base, True);
		while (tile >= 1) {
			tile /= w->triangles.base;
			digitOffsetX += w->triangles.digitOffset.x;
			i++;
		}
		digitOffsetY = (orient == UP) ? w->triangles.digitOffset.y +
			w->triangles.tileSize.y / 8 + w->triangles.delta.y :
			w->triangles.digitOffset.y - w->triangles.tileSize.y +
			- w->triangles.tileSize.y / 7 - 2;
		if (!w->triangles.corners)
			digitOffsetY += -w->triangles.delta.y / 2 +
				((orient == UP) ? w->triangles.tileSize.y / 20 : 1);
		DRAWTEXT(w, *dr, textGC,
			dx - digitOffsetX,
			dy + w->triangles.tileSize.y / 2 + digitOffsetY,
			buf, i);
	}
}

void
drawAllTiles(const TrianglesWidget w)
{
	int pos;

	for (pos = 0; pos < w->triangles.sizeSize; pos++) {
		int row = toRow(w, pos);
		int trbl = toTrBl(w, pos, row);
		int tlbr = toTlBr(w, pos, row);
		int orient = toOrient(w, row, trbl, tlbr);

		drawTile(w, pos, orient, (w->triangles.tileOfPosition[pos] <= 0),
			(w->triangles.tileOfPosition[pos] <= 0), FALSE, 0, 0);
	}
}

static int
movableCornerTile(TrianglesWidget 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->triangles.currentRow[l] == w->triangles.spaceRow[DOWN][l] &&
				w->triangles.currentRow[l] == w->triangles.spaceRow[UP][l]) {
			if (rowType == BLOCKED) {
				rowType = l;
			} else {
				return SPACE;
			}
		}
	}
	return rowType;
}

static int
movableNoCornTile(TrianglesWidget w)
{
	int rowType;
	if (w->triangles.currentPosition == w->triangles.spacePosition[UP])
		return SPACE;
	rowType = nextNoCornDir(w, w->triangles.currentPosition);
	if (rowType < 0)
		return BLOCKED;
	return rowType;
}

#ifdef RHOMBUS
static void
drawRhombus(TrianglesWidget w, int pos, int rowType, int offsetX, int offsetY)
{
	int dx, dy, row = toRow(w, pos);

	dy = row * w->triangles.offset.y + w->triangles.delta.y + 2
		+ w->triangles.puzzleOffset.y - 2;
	dx = (pos - fromRow(w, row - 1)
		- row + w->triangles.sizeX) * w->triangles.offset.x / 2
		+ w->triangles.delta.x / 2 + w->triangles.puzzleOffset.x - 1;
	dx += offsetX;
	dy += offsetY;
	rhombusList[rowType][0].x = dx;
	rhombusList[rowType][0].y = dy;
	XFillPolygon(XtDisplay(w), XtWindow(w), w->triangles.inverseGC[2],
		rhombusList[rowType], 4,
		Convex, CoordModePrevious);
	XDrawLines(XtDisplay(w), XtWindow(w), w->triangles.inverseGC[2],
		rhombusList[rowType], 5,
		CoordModePrevious);
}

/* Rhombus type is determined by its narrowist "width" to reuse constants */
static int
rhombusType(TrianglesWidget w)
{
	if (w->triangles.spaceRow[0][ROW] != w->triangles.spaceRow[1][ROW])
		return ROW;
	if (w->triangles.spaceRow[0][TRBL] != w->triangles.spaceRow[1][TRBL])
		return TRBL;
	if (w->triangles.spaceRow[0][TLBR] != w->triangles.spaceRow[1][TLBR])
		return TLBR;
	DISPLAY_WARNING("Rhombus Type: unknown");
	return -1;
}
#endif

#ifdef ANIMATE
static int
countTiles(TrianglesWidget w, int dir, int rowType)
{
	switch (rowType) {
	case TLBR:
		return w->triangles.spaceRow[dir][TRBL] -
			w->triangles.currentRow[TRBL] +
			w->triangles.spaceRow[(dir == UP) ? DOWN : UP][ROW] -
			w->triangles.currentRow[ROW];
	case TRBL:
		return w->triangles.spaceRow[dir][TLBR] -
			w->triangles.currentRow[TLBR] +
			w->triangles.spaceRow[(dir == UP) ? DOWN : UP][ROW] -
			w->triangles.currentRow[ROW];
	case ROW:
		return w->triangles.spaceRow[dir][TRBL] -
			w->triangles.currentRow[TRBL] +
			w->triangles.currentRow[TLBR] -
			w->triangles.spaceRow[(dir == UP) ? DOWN : UP][TLBR];
	default:
		{
			char *buf;

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

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

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

static void
animateCornerSlide(TrianglesWidget w, int dir, int fast, Boolean logMoves)
{
	int aTile, numTiles, direction = dir, rowType;
	int fillBeginPos, fillBeginPos2, fillBeginRow;
	int fillBeginOrient, fillBeginOrientOp;
	int pos, pos2, posNext, orient, orientNext;

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

		intCat(&buf, "animateCornerSlide: rowType ", rowType);
		DISPLAY_WARNING(buf);
		free(buf);
		numTiles = 0;
	} else {
		numTiles = countTiles(w, dir, rowType);
		direction = dirTiles(w, dir, rowType);
	}
	if (numTiles < 0)
		numTiles = -numTiles;
	if (dir == UP)
		orient = (w->triangles.spacePosition[UP] +
			((rowType == TRBL) ? 2 : 0) >
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
	else
		orient = (w->triangles.spacePosition[UP] +
			((rowType == TRBL) ? 1 : 0) <
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
	fillBeginPos = tileNFromSpace(w, numTiles, rowType, orient,
		(dir == UP) ? DOWN : UP);
	fillBeginRow = toRow(w, fillBeginPos);
	fillBeginOrient = toOrient(w, fillBeginRow,
		toTrBl(w, fillBeginPos, fillBeginRow),
		toTlBr(w, fillBeginPos, fillBeginRow));
	fillBeginOrientOp = (fillBeginOrient == UP) ? DOWN : UP;
	fillBeginPos2 = w->triangles.spacePosition[fillBeginOrientOp];
#ifdef RHOMBUS
	{
		int inc = 0;
		int gapI = 0, moveI = 0;
		/*int gapJ = 0, moveJ = 0;*/

		gapI = w->triangles.tileSize.x * fast / w->triangles.numSlices;
		moveI = w->triangles.tileSize.x + w->triangles.delta.x;
		/*gapJ = 0;
		moveJ = w->triangles.tileSize.y + w->triangles.delta.y;*/
		if (gapI == 0)
			gapI++;
		for (inc = 0; inc < moveI + gapI; inc += gapI) {
			if (inc > moveI)
				inc = moveI;
			for (aTile = 0; aTile < numTiles; aTile++) {
				posNext = tileNFromSpace(w, aTile + 1, rowType,
					orient, !dir);
				orientNext = (aTile & 1) ? orient : !orient;
			}
			/* Calculate deltas */
			if (numTiles > 0)
				drawTile(w, posNext, orientNext, False, False, FALSE,
					1, 1);
			/* Erase old slivers */
		}
		FLUSH(w);
		Sleep((unsigned int) (w->triangles.delay / fast));
	}
#else
	{
		int pixNo = 0;
		int pixX = 0, pixY = 0;
		int oldPixX = 0, oldPixY = 0;
		int fromdx = 0, fromdy = 0;
		int todx = 0, tody = 0;
		int dx, dy, row;
		int firstRow, firstOrient;
		int *rowPos;
		int i, k;
		Boolean slideDone = False;

		if (!(rowPos = (int *)
				malloc(sizeof (int) *
					(size_t) (numTiles + 2)))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		rowPos[2] = tileNFromSpace(w, 1, rowType, orient,
			(dir == UP) ? DOWN : UP);
		firstRow = toRow(w, rowPos[2]);
		firstOrient = toOrient(w, firstRow,
			toTrBl(w, rowPos[2], firstRow),
			toTlBr(w, rowPos[2], firstRow));
		rowPos[0] = w->triangles.spacePosition[firstOrient];
		rowPos[1] = w->triangles.spacePosition[(firstOrient == UP) ?
			DOWN : UP];
		for (i = 1; i < numTiles; i++) {
			rowPos[i + 2] = tileNFromSpace(w,
				i + 1, rowType, orient,
				(dir == UP) ? DOWN : UP);
		}
		row = toRow(w, rowPos[2]);
		fromdx = cartesianX(w, rowPos[2], row);
		fromdy = cartesianY(w, row, firstOrient);
		row = toRow(w, rowPos[0]);
		todx = cartesianX(w, rowPos[0], row);
		tody = cartesianY(w, row, firstOrient);
		dx = todx - fromdx;
		dy = tody - fromdy;
		i = 0;
		while (!slideDone) {
			oldPixX = pixX;
			oldPixY = pixY;
			slideDone = bresenhamLine(0, 0, dx, dy,
				&pixX, &pixY, &pixNo);
			if (!slideDone) {
				for (k = 2; k < numTiles + 2; k++)
					drawTile(w, rowPos[k],
						(firstOrient + k) & 1,
						True, True, FALSE, oldPixX, oldPixY);
				for (k = 2; k < numTiles + 2; k++)
					drawTile(w, rowPos[k],
						(firstOrient + k) & 1,
						False, False, FALSE, pixX, pixY);
				FLUSH(w);
				if (i % 8 == 0)
					Sleep((unsigned int) (w->triangles.delay /
						fast));
			}
			i++;
		}
		for (k = 2; k < numTiles + 2; k++)
			drawTile(w, rowPos[k],
				(firstOrient + k) & 1,
				True, True, FALSE, oldPixX, oldPixY);
		free(rowPos);
	}
#endif
	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, orient,
			(dir == UP) ? DOWN : UP);
		orientNext = ((aTile & 1) == 1) ? orient :
			(orient == UP) ? DOWN : UP;
		if (orientNext == fillBeginOrientOp)
			fillBeginPos2 = posNext;
	}
	pos2 = w->triangles.spacePosition[orient];
	pos = w->triangles.spacePosition[(orient == UP) ? DOWN : UP];
	for (aTile = 0; aTile < numTiles; aTile++) {
		posNext = tileNFromSpace(w, aTile + 1, rowType, orient,
			(dir == UP) ? DOWN : UP);
		w->triangles.tileOfPosition[pos] =
			w->triangles.tileOfPosition[posNext];
		drawTile(w, pos,
			(orient + aTile + 1) & 1,
			False, False, FALSE, 0, 0);
		pos = pos2;
		pos2 = posNext;
	}
	w->triangles.spacePosition[fillBeginOrient] = fillBeginPos;
	w->triangles.spacePosition[fillBeginOrientOp] = fillBeginPos2;
	if (fillBeginOrient == UP) {
		w->triangles.tileOfPosition[fillBeginPos] = 0;
		w->triangles.tileOfPosition[fillBeginPos2] = -1;
	} else {
		w->triangles.tileOfPosition[fillBeginPos] = -1;
		w->triangles.tileOfPosition[fillBeginPos2] = 0;
	}
	w->triangles.spaceRow[fillBeginOrient][ROW] = toRow(w, fillBeginPos);
	w->triangles.spaceRow[fillBeginOrient][TRBL] = toTrBl(w, fillBeginPos,
		w->triangles.spaceRow[fillBeginOrient][ROW]);
	w->triangles.spaceRow[fillBeginOrient][TLBR] = toTlBr(w, fillBeginPos,
		w->triangles.spaceRow[fillBeginOrient][ROW]);
	w->triangles.spaceRow[fillBeginOrientOp][ROW] = toRow(w, fillBeginPos2);
	w->triangles.spaceRow[fillBeginOrientOp][TRBL] = toTrBl(w, fillBeginPos2,
		w->triangles.spaceRow[fillBeginOrientOp][ROW]);
	w->triangles.spaceRow[fillBeginOrientOp][TLBR] = toTlBr(w, fillBeginPos2,
		w->triangles.spaceRow[fillBeginOrientOp][ROW]);
#ifdef RHOMBUS
	drawRhombus(w, w->triangles.spacePosition[UP], rhombusType(w), 1, 1);
#endif
}

static void
animateNoCornSlide(TrianglesWidget w, int fast, Boolean logMoves)
{
	int direction;
	int pos, posNext, row;
	int pixNo = 0;
	int pixX = 0, pixY = 0;
	int fromdx = 0, fromdy = 0;
	int todx = 0, tody = 0;
	int dx, dy;
	int orient;
	int i;
	int oldPixX = 0, oldPixY = 0;
	Boolean slideDone = False;

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

		intCat(&buf, "animateNoCornSlide: direction ", direction);
		DISPLAY_WARNING(buf);
		free(buf);
		return;
	}
	pos = tile1From(w, w->triangles.spacePosition[UP], direction);
	row = toRow(w, pos);
	orient = toOrient(w, row,
		toTrBl(w, pos, row), toTlBr(w, pos, row));
	fromdx = cartesianX(w, pos, row);
	fromdy = cartesianY(w, row, orient);
	todx = cartesianX(w, w->triangles.spacePosition[UP],
		w->triangles.spaceRow[UP][ROW]);
	tody = cartesianY(w, w->triangles.spaceRow[UP][ROW],
		((orient == DOWN) ? 1 : 0));
	if (orient == DOWN)
		tody += (4 * w->triangles.tileSize.y / 3 + 2);
	else
		fromdy += (4 * w->triangles.tileSize.y / 3 + 2);
	dx = todx - fromdx;
	dy = tody - fromdy;
	i = 0;
	drawTile(w, pos, orient,
		True, True, FALSE, pixX + 1, pixY + 1);
	while (!slideDone) {
		oldPixX = pixX;
		oldPixY = pixY;
		slideDone = bresenhamLine(0, 0, dx, dy,
			&pixX, &pixY, &pixNo);
		if (!slideDone) {
			drawTile(w, pos, orient,
				True, True, FALSE, oldPixX, oldPixY);
			drawTile(w, pos, orient,
				False, False, FALSE, pixX, pixY);
			FLUSH(w);
			if (i % 8 == 0)
				Sleep((unsigned int) (w->triangles.delay /
					fast));
		}
		i++;
	}
	drawTile(w, pos, orient,
		True, True, FALSE, oldPixX, oldPixY);
	if (logMoves) {
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, direction);
		flushMoves(w, &redo, FALSE);
	}
	posNext = w->triangles.spacePosition[UP];
	w->triangles.spacePosition[UP] = pos;
	w->triangles.tileOfPosition[posNext] =
		w->triangles.tileOfPosition[pos];
	w->triangles.tileOfPosition[pos] = 0;
	w->triangles.spaceRow[UP][ROW] = toRow(w, pos);
	w->triangles.spaceRow[UP][TRBL] = toTrBl(w, pos,
		w->triangles.spaceRow[UP][ROW]);
	w->triangles.spaceRow[UP][TLBR] = toTlBr(w, pos,
		w->triangles.spaceRow[UP][ROW]);
	pos = posNext;
	row = toRow(w, pos);
	orient = toOrient(w, row, toTrBl(w, pos, row), toTlBr(w, pos, row));
	fromdx = cartesianX(w, pos, row);
	fromdy = cartesianY(w, row, orient);
	todx = cartesianX(w, w->triangles.spacePosition[UP],
		w->triangles.spaceRow[UP][ROW]);
	tody = cartesianY(w, w->triangles.spaceRow[UP][ROW],
		((orient == DOWN) ? 1 : 0));
	if (orient == DOWN)
		tody += (4 * w->triangles.tileSize.y / 3 + 2);
	else
		fromdy += (4 * w->triangles.tileSize.y / 3 + 2);
	drawTile(w, pos, orient,
		False, False, FALSE, 0, 0);
}
#endif

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

	w->triangles.sizeSize = w->triangles.sizeY * w->triangles.sizeY
		+ 2 * w->triangles.sizeY * (w->triangles.sizeX - 1);
	w->triangles.prevSizeSize = (w->triangles.sizeY - 1) * (w->triangles.sizeY - 1)
		+ 2 * (w->triangles.sizeY - 1) * (w->triangles.sizeX - 1);
	if (w->triangles.tileOfPosition)
		free(w->triangles.tileOfPosition);
	if (!(w->triangles.tileOfPosition = (int *)
			malloc(sizeof (int) *
				(size_t) w->triangles.sizeSize))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	if (startPosition)
		free(startPosition);
	if (!(startPosition = (int *)
			malloc(sizeof (int) *
				(size_t) w->triangles.sizeSize))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	/*for (pos = 0; pos < w->triangles.sizeSize - 2; pos++)
		w->triangles.tileOfPosition[pos] = pos + 1;
	w->triangles.spacePosition[UP] = w->triangles.sizeSize - 1;
	w->triangles.spaceRow[UP][TRBL] = w->triangles.sizeX - 1; *//* i *//*
	w->triangles.spaceRow[UP][TLBR] = 0; *//* j *//*
	w->triangles.spaceRow[UP][ROW] = w->triangles.sizeX - 1; *//* k *//*
	w->triangles.tileOfPosition[w->triangles.sizeSize - 1] = 0;
	if (w->triangles.sizeX > 1) {
		w->triangles.spacePosition[DOWN] = w->triangles.sizeSize - 2;
		w->triangles.spaceRow[DOWN][TRBL] = w->triangles.sizeX - 2; *//* i *//*
		w->triangles.spaceRow[DOWN][TLBR] = 0; *//* j *//*
		w->triangles.spaceRow[DOWN][ROW] = w->triangles.sizeX - 1; *//* k *//*
		w->triangles.tileOfPosition[w->triangles.sizeSize - 2] = -1;
	}*/
	if (w->triangles.sizeY < 2) {
		for (pos = 0; pos < w->triangles.sizeSize; pos++) {
			if (pos == 0)
				w->triangles.tileOfPosition[pos] = 0;
			else if (w->triangles.corners && pos == 1)
				w->triangles.tileOfPosition[pos] = -1;
			else
				w->triangles.tileOfPosition[pos] = pos - ((w->triangles.corners) ? 2 : 1) + 1;
		}
		w->triangles.spacePosition[UP] = 0;
		w->triangles.spaceRow[UP][TRBL] = 0; /* i */
		w->triangles.spaceRow[UP][TLBR] = w->triangles.sizeX - 1; /* j */
		w->triangles.spaceRow[UP][ROW] = 0; /* k */
		if (w->triangles.corners) {
			if (w->triangles.sizeX > 1) {
				w->triangles.spacePosition[DOWN] = 1;
				w->triangles.spaceRow[DOWN][TLBR] = w->triangles.sizeX - 2; /* j */
			} else {
				w->triangles.spacePosition[DOWN] = 0;
				w->triangles.spaceRow[DOWN][TLBR] = 0; /* j */
			}
			w->triangles.spaceRow[DOWN][TRBL] = 0; /* i */
			w->triangles.spaceRow[DOWN][ROW] = 0; /* k */
		}
	} else {
		if (!w->triangles.corners
				&& w->triangles.sizeX == 1 && w->triangles.sizeY == 3) {
			for (pos = 0; pos < w->triangles.sizeSize; pos++)
				w->triangles.tileOfPosition[pos] = start13[pos];
			/*w->triangles.tileOfPosition[0] = 1;
			w->triangles.tileOfPosition[1] = 4;
			w->triangles.tileOfPosition[2] = 7;
			w->triangles.tileOfPosition[3] = 6;
			w->triangles.tileOfPosition[4] = 0;
			w->triangles.tileOfPosition[5] = 5;
			w->triangles.tileOfPosition[6] = 3;
			w->triangles.tileOfPosition[7] = 2;
			w->triangles.tileOfPosition[8] = 8;*/
		} else if (!w->triangles.corners
				&& w->triangles.sizeX == 2 && w->triangles.sizeY == 2) {
			for (pos = 0; pos < w->triangles.sizeSize; pos++)
				w->triangles.tileOfPosition[pos] = start22[pos];
			/*w->triangles.tileOfPosition[0] = 3;
			w->triangles.tileOfPosition[1] = 6;
			w->triangles.tileOfPosition[2] = 5;
			w->triangles.tileOfPosition[3] = 0;
			w->triangles.tileOfPosition[4] = 4;
			w->triangles.tileOfPosition[5] = 2;
			w->triangles.tileOfPosition[6] = 1;
			w->triangles.tileOfPosition[7] = 7;*/
		} else {
			int parityCalc = w->triangles.sizeY % 4;
			Boolean paritySwap = (parityCalc == 0) ||
				(parityCalc == 1 && w->triangles.sizeX % 2 == 1) ||
				(parityCalc == 3 && w->triangles.sizeX % 2 == 0);
			for (pos = 0; pos < w->triangles.sizeSize; pos++) {
				int row, trbl, tlbr;

				row = toRow(w, pos);
				trbl = toTrBl(w, pos, row);
				tlbr = toTlBr(w, pos, row);
				if (pos == w->triangles.prevSizeSize)
					w->triangles.tileOfPosition[pos] = 0;
				else if (pos == w->triangles.prevSizeSize + 1) {
					if (w->triangles.corners)
						w->triangles.tileOfPosition[pos] = -1;
					else
						w->triangles.tileOfPosition[pos] = pos;
				} else if (!w->triangles.corners && pos == w->triangles.sizeSize - 1)
					w->triangles.tileOfPosition[pos] = pos;
				else if (!w->triangles.corners && paritySwap && pos == w->triangles.sizeSize - 2)
					w->triangles.tileOfPosition[pos] = pos;
				else if (!w->triangles.corners && paritySwap && pos == w->triangles.prevSizeSize + 2)
					w->triangles.tileOfPosition[pos] = pos;
				else
					w->triangles.tileOfPosition[pos] = toPosition(w, row, tlbr, trbl - 1);
			}
		}
		w->triangles.spacePosition[UP] =
			fromRow(w, w->triangles.sizeY - 2);
		w->triangles.spaceRow[UP][TRBL] = 0; /* i */
		w->triangles.spaceRow[UP][TLBR] =
			w->triangles.sizeX + w->triangles.sizeY - 2; /* j */
		w->triangles.spaceRow[UP][ROW] = w->triangles.sizeY - 1; /* k */
		if (w->triangles.corners) {
			w->triangles.spacePosition[DOWN] =
				w->triangles.spacePosition[UP] + 1;
			w->triangles.spaceRow[DOWN][TRBL] =
				w->triangles.spaceRow[UP][TRBL]; /* i */
			w->triangles.spaceRow[DOWN][TLBR] =
				w->triangles.spaceRow[UP][TLBR] - 1; /* j */
			w->triangles.spaceRow[DOWN][ROW] =
				w->triangles.spaceRow[UP][ROW]; /* k */
		}
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->triangles.currentPosition = -1;
	w->triangles.started = FALSE;
}

static void
drawFrameLR(const TrianglesWidget w, Pixmap dr)
{
	GC gc = w->triangles.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->triangles.stippleFrame && w->triangles.grainLR != (Pixmap) NULL) {
		XSetStipple(display, w->triangles.frameGC, w->triangles.grainLR);
		XSetFillStyle(display, w->triangles.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->triangles.frameGC, FillSolid);
#endif
	/* Bottom */
	tempList[0].x = w->triangles.sumX + w->triangles.frameThickness.x + 1;
	tempList[0].y = w->triangles.sumY + w->triangles.frameThickness.y + 1;
	tempList[1].x = w->triangles.offsetX - w->triangles.frameThickness.x - 1;
	tempList[1].y = w->triangles.sumY + w->triangles.frameThickness.y + 1;
	tempList[2].x = w->triangles.offsetX - 1;
	tempList[2].y = w->triangles.sumY + 1;
	tempList[3].x = w->triangles.sumX + 1;
	tempList[3].y = w->triangles.sumY + 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
	tempList[0].x = w->triangles.sumX1 - 1;
	tempList[0].y = w->triangles.offsetY - 1;
	tempList[1].x = w->triangles.sumX2 + 1;
	tempList[1].y = w->triangles.offsetY - 1;
	tempList[2].x = w->triangles.sumX2 + 1;
	tempList[2].y = w->triangles.offsetY - (int) (sqrt_3 * w->triangles.frameThickness.y) - 1;
	tempList[3].x = w->triangles.sumX1 - 1;
	tempList[3].y = w->triangles.offsetY - (int) (sqrt_3 * w->triangles.frameThickness.y) - 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 TrianglesWidget w, Pixmap dr)
{
	GC gc = w->triangles.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w); 
	if (w->triangles.stippleFrame && w->triangles.grainTLBR != (Pixmap) NULL) {
		XSetStipple(display, w->triangles.frameGC, w->triangles.grainTLBR);
		XSetFillStyle(display, w->triangles.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->triangles.frameGC, FillSolid);
#endif
	/* Right */
	tempList[0].x = w->triangles.sumX + w->triangles.frameThickness.x + 1;
	tempList[0].y = w->triangles.sumY + w->triangles.frameThickness.y + 1;
	tempList[1].x = w->triangles.sumX2 + 1;
	tempList[1].y = w->triangles.offsetY - (int) (sqrt_3 * w->triangles.frameThickness.y) - 1;
	tempList[2].x = w->triangles.sumX2 + 1;
	tempList[2].y = w->triangles.offsetY - 1;
	tempList[3].x = w->triangles.sumX + 1;
	tempList[3].y = w->triangles.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
drawFrameTRBL(const TrianglesWidget w, Pixmap dr)
{
	GC gc = w->triangles.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->triangles.stippleFrame && w->triangles.grainTRBL != (Pixmap) NULL) {
		XSetStipple(display, w->triangles.frameGC, w->triangles.grainTRBL);
		XSetFillStyle(display, w->triangles.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->triangles.frameGC, FillSolid);
#endif
	/* Left */
	tempList[0].x = w->triangles.offsetX - w->triangles.frameThickness.x - 1;
	tempList[0].y = w->triangles.sumY + w->triangles.frameThickness.y + 1;
	tempList[1].x = w->triangles.sumX1 - 2;
	tempList[1].y = w->triangles.offsetY - (int) (sqrt_3 * w->triangles.frameThickness.y) - 1;
	tempList[2].x = w->triangles.sumX1 - 2;
	tempList[2].y = w->triangles.offsetY - 1;
	tempList[3].x = w->triangles.offsetX - 1;
	tempList[3].y = w->triangles.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
drawFrame(TrianglesWidget w, Pixmap dr, Boolean focus)
{
	Point tempList[5];
	GC brighterGC = (focus) ? w->triangles.inverseGC[1] :
		w->triangles.inverseGC[2];
	GC darkerGC = (focus) ? w->triangles.inverseGC[3] :
		w->triangles.inverseGC[2];

	w->triangles.offsetX = w->triangles.puzzleOffset.x;
	w->triangles.offsetY = w->triangles.puzzleOffset.y;
	w->triangles.sumX = (w->triangles.sizeX + w->triangles.sizeY - 1)
		* w->triangles.offset.x + w->triangles.delta.x + 1;
	w->triangles.sumY = w->triangles.sizeY
		* w->triangles.offset.y + w->triangles.delta.y + 1;
	w->triangles.sumX1 = (w->triangles.sizeY * w->triangles.offset.x
		+ w->triangles.delta.x) / 2 + w->triangles.offsetX;
	w->triangles.sumX2 = w->triangles.sumX1
		+ (w->triangles.sizeX - 1) * w->triangles.offset.x;
	w->triangles.sumX += w->triangles.offsetX;
	w->triangles.sumY += w->triangles.offsetY;
	if (w->triangles.offsetY > 0) {
		FILLRECTANGLE(w, dr, w->triangles.textGC,
			0, 0, w->core.width, w->triangles.offsetY);
	}
	if (w->triangles.sumY < w->core.height) {
		FILLRECTANGLE(w, dr, w->triangles.textGC,
			0, w->triangles.sumY + 1,
			w->core.width, w->core.height - w->triangles.sumY);
	}
	if (w->triangles.offsetX > 0) {
		FILLRECTANGLE(w, dr, w->triangles.textGC,
			0, 0, w->triangles.offsetX, w->core.height);
	}
	if (w->triangles.sumX - 2 < w->core.width) {
		FILLRECTANGLE(w, dr, w->triangles.textGC,
			w->triangles.sumX - 2, 0,
			w->core.width - w->triangles.sumX + 2, w->core.height);
	}
	tempList[0].x = w->triangles.offsetX - 1;
	tempList[0].y = w->triangles.offsetY;
	tempList[1].x = w->triangles.sumX1 - 3;
	tempList[1].y = w->triangles.offsetY;
	tempList[2].x = w->triangles.offsetX - 2;
	tempList[2].y = w->triangles.sumY;
	tempList[3].x = w->triangles.offsetX - 1;
	tempList[3].y = w->triangles.offsetY;
	POLYGON(w, dr, w->triangles.textGC, w->triangles.textGC,
		tempList, 3, True, True);
	tempList[0].x = w->triangles.sumX - 2;
	tempList[0].y = w->triangles.offsetY - 1;
	tempList[1].x = w->triangles.sumX - 2;
	tempList[1].y = w->triangles.sumY + 1;
	tempList[2].x = w->triangles.sumX2;
	tempList[2].y = w->triangles.offsetY - 1;
	tempList[3].x = w->triangles.sumX - 2;
	tempList[3].y = w->triangles.offsetY - 1;
	POLYGON(w, dr, w->triangles.textGC, w->triangles.textGC,
		tempList, 3, True, True);
	tempList[0].x = w->triangles.offsetX;
	tempList[0].y = w->triangles.sumY + 1;
	tempList[1].x = w->triangles.sumX1 - 3;
	tempList[1].y = w->triangles.offsetY - 1;
	POLYLINE(w, dr, darkerGC, tempList, 2, True);
	tempList[0].x = w->triangles.sumX2 - 1;
	tempList[0].y = w->triangles.offsetY - 1;
	tempList[1].x = w->triangles.sumX - 1;
	tempList[1].y = w->triangles.sumY + 1;
	tempList[2].x = w->triangles.offsetX + 1;
	tempList[2].y = w->triangles.sumY + 1;
	POLYLINE(w, dr, brighterGC, tempList, 3, True);
	tempList[0].x = w->triangles.offsetX + 1;
	tempList[0].y = w->triangles.sumY + 1;
	tempList[1].x = w->triangles.sumX1 - 2;
	tempList[1].y = w->triangles.offsetY - 1;
	POLYLINE(w, dr, darkerGC, tempList, 2, True);
	tempList[0].x = w->triangles.sumX2 - 1;
	tempList[0].y = w->triangles.offsetY;
	tempList[1].x = w->triangles.sumX - 1;
	tempList[1].y = w->triangles.sumY;
	tempList[2].x = w->triangles.offsetX + 1;
	tempList[2].y = w->triangles.sumY;
	POLYLINE(w, dr, brighterGC, tempList, 3, True);
	tempList[0].x = w->triangles.offsetX + 1;
	tempList[0].y = w->triangles.sumY;
	tempList[1].x = w->triangles.sumX1 - 2;
	tempList[1].y = w->triangles.offsetY;
	POLYLINE(w, dr, darkerGC, tempList, 2, True);
	tempList[0].x = w->triangles.offsetX;
	tempList[0].y = w->triangles.sumY;
	tempList[1].x = w->triangles.sumX1 - 3;
	tempList[1].y = w->triangles.offsetY;
	POLYLINE(w, dr, darkerGC, tempList, 2, True);
	tempList[0].x = w->triangles.sumX2;
	tempList[0].y = w->triangles.offsetY - 1;
	tempList[1].x = w->triangles.sumX- 2;
	tempList[1].y = w->triangles.sumY + 1;
	tempList[2].x = w->triangles.offsetX;
	tempList[2].y = w->triangles.sumY + 1;
	POLYLINE(w, dr, brighterGC, tempList, 3, True);
	drawFrameLR(w, dr);
	drawFrameTLBR(w, dr);
	drawFrameTRBL(w, dr);
}

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

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

static void
moveCornerTiles(TrianglesWidget w, int from, int orient, int fast)
{
#ifdef ANIMATE
	if (fast != INSTANT && w->triangles.delay > 0) {
		w->triangles.currentPosition = from;
		w->triangles.currentRow[ROW] = toRow(w, from);
		w->triangles.currentRow[TRBL] =
			toTrBl(w, from, w->triangles.currentRow[ROW]);
		w->triangles.currentRow[TLBR] =
			toTlBr(w, from, w->triangles.currentRow[ROW]);
		w->triangles.currentPositionOrient = toOrient(w, w->triangles.currentRow[ROW],
			w->triangles.currentRow[TRBL],
			w->triangles.currentRow[TLBR]);
		if (w->triangles.currentPosition <
			w->triangles.spacePosition[w->triangles.currentPositionOrient]) {
			animateCornerSlide(w, DOWN, fast, False);
		} else if (w->triangles.currentPosition >
			w->triangles.spacePosition[w->triangles.currentPositionOrient]) {
			animateCornerSlide(w, UP, fast, False);
		} else
			return;
		w->triangles.currentPosition = -1;
	} else
#endif
	{
		int tempTile = w->triangles.tileOfPosition[from];

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

static void
moveNoCornTiles(TrianglesWidget w, int from, int fast)
{
	int orient = UP, drawOrient;

#ifdef ANIMATE
	if (fast != INSTANT && w->triangles.delay > 0) {
		w->triangles.currentPosition = from;
		w->triangles.currentRow[ROW] = toRow(w, from);
		w->triangles.currentRow[TRBL] =
			toTrBl(w, from, w->triangles.currentRow[ROW]);
		w->triangles.currentRow[TLBR] =
			toTlBr(w, from, w->triangles.currentRow[ROW]);
		if (w->triangles.currentPosition <
			w->triangles.spacePosition[UP]) {
			animateNoCornSlide(w, fast, False);
		} else if (w->triangles.currentPosition >
			w->triangles.spacePosition[UP]) {
			animateNoCornSlide(w, fast, False);
		} else
			return;
		w->triangles.currentPosition = -1;
	} else
#endif
	{
		int tempTile = w->triangles.tileOfPosition[from];

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

static int
cornerDirToOrient(TrianglesWidget w, int direction)
{
	switch (direction) {
	case TR:
		return (w->triangles.spacePosition[UP] + 2 >
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
	case RIGHT:
	case BR:
		return (w->triangles.spacePosition[UP] <
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
	case BL:
		return (w->triangles.spacePosition[UP] + 1 <
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
	case LEFT:
	case TL:
		return (w->triangles.spacePosition[UP] >
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
	default:
		return -1;
	}
}

static Boolean
checkMoveCornerTilesDir(TrianglesWidget w, int direction)
{
	switch (direction) {
	case TR:
		return (w->triangles.spaceRow[UP][TRBL] ==
				w->triangles.spaceRow[DOWN][TRBL] &&
				w->triangles.spaceRow[UP][ROW] !=
				w->triangles.sizeY - 1);
	case RIGHT:
		return (w->triangles.spaceRow[UP][ROW] ==
				w->triangles.spaceRow[DOWN][ROW] &&
				w->triangles.spaceRow[UP][TRBL] != 0);
	case BR:
		return (w->triangles.spaceRow[UP][TLBR] ==
				w->triangles.spaceRow[DOWN][TLBR] &&
				w->triangles.spaceRow[UP][TRBL] != 0);
	case BL:
		return (w->triangles.spaceRow[UP][TRBL] ==
				w->triangles.spaceRow[DOWN][TRBL] &&
				w->triangles.spaceRow[UP][TLBR] != 0);
	case LEFT:
		return (w->triangles.spaceRow[UP][ROW] ==
				w->triangles.spaceRow[DOWN][ROW] &&
				w->triangles.spaceRow[UP][TLBR] != 0);
	case TL:
		return (w->triangles.spaceRow[UP][TLBR] ==
				w->triangles.spaceRow[DOWN][TLBR] &&
				w->triangles.spaceRow[UP][ROW] !=
				w->triangles.sizeY - 1);
	default:
		return False;
	}
}

static int
moveCornerDir(TrianglesWidget w, int direction, int fast)
{

	if (checkMoveCornerTilesDir(w, direction)) {
		int orient = cornerDirToOrient(w, direction);
		int rowType = toRowType(direction);
		int heading = toDirHeading(direction);
		int from = tileNFromSpace(w, 1, rowType, orient, heading);

		moveCornerTiles(w, from, !orient, fast);
		return TRUE;
	}
	return FALSE;
}

static int
moveNoCornDir(TrianglesWidget w, int direction, int fast)
{
	int position;

	position = tile1From(w, w->triangles.spacePosition[UP], direction);
	if (position >= 0) {
		moveNoCornTiles(w, position, fast);
		return TRUE;
	}
	return FALSE;
}

int
movePuzzleDir(TrianglesWidget w, const int direction, const int fast)
{
	int dir = direction;

	if (direction == TOP) {
		if (w->triangles.corners)
			return FALSE;
		if (w->triangles.spaceRow[UP][TRBL] + w->triangles.spaceRow[UP][TLBR] == w->triangles.spaceRow[UP][ROW])
			dir = TR; /* or TL */
		else
			return FALSE;
	} else if (direction == BOTTOM) {
		if (w->triangles.corners)
			return FALSE;
		if (w->triangles.spaceRow[UP][TRBL] + w->triangles.spaceRow[UP][TLBR] == w->triangles.spaceRow[UP][ROW])
			return FALSE;
		else
			dir = BR; /* or BL */
	}
	if (w->triangles.corners && moveCornerDir(w, dir, fast)) {
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, dir);
		flushMoves(w, &redo, FALSE);
		return TRUE;
	} else if (!w->triangles.corners && moveNoCornDir(w, dir, fast)) {
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, dir);
		flushMoves(w, &redo, FALSE);
		return TRUE;
	}
	return FALSE;
}

#ifndef WINVER
static
#endif
int
movePuzzle(TrianglesWidget 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->triangles.sizeY <= MIN_TILES)
				return FALSE;
			reason = ACTION_DECY;
			break;
		case LEFT:
			if (w->triangles.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 (!movePuzzleDir(w, direction, NORMAL)) {
		setPuzzle(w, ACTION_BLOCKED);
		return FALSE;
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
	return TRUE;
}

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

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

static void
discreteCornerMoves(TrianglesWidget w, int dir, int fast)
{
	int rowType, orient, next;
	char *buf;

	rowType = movableCornerTile(w);
	if (rowType < 0) {
		intCat(&buf, "discreteCornerMoves: rowType ", rowType);
		DISPLAY_WARNING(buf);
		free(buf);
		DISPLAY_WARNING(buf);
	}
	if (dir == DOWN) {
		orient = (w->triangles.spacePosition[UP] +
			((rowType == TRBL) ? 1 : 0) <
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
		next = tileNFromSpace(w, 1, rowType, orient, UP);
		orient = (orient == UP) ? DOWN : UP;
		moveCornerTiles(w, next, 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,
					"discreteCornerMoves: rowType ",
					rowType);
				DISPLAY_WARNING(buf);
				free(buf);
				return;
			}
		}
	} else {
		orient = (w->triangles.spacePosition[UP] +
			((rowType == TRBL) ? 2 : 0) >
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
		next = tileNFromSpace(w, 1, rowType, orient, DOWN);
		orient = (orient == UP) ? DOWN : UP;
		moveCornerTiles(w, next, 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,
					"discreteCornerMoves: rowType ",
					rowType);
				DISPLAY_WARNING(buf);
				free(buf);
				return;
			}
		}
	}
}

static void
discreteNoCornMoves(TrianglesWidget w, int dir, int fast)
{
	int position = tile1From(w, w->triangles.spacePosition[UP], dir);

	moveNoCornTiles(w, position, fast);
	setPuzzle(w, ACTION_MOVED);
	setMove(&undo, dir);
	flushMoves(w, &redo, FALSE);
}

static int
positionToTile(TrianglesWidget w, int positionX, int positionY,
		int *row, int *trbl, int *tlbr)
{
	int sumY, sumX1, sumX2;
	int x = positionX, y = positionY;

	sumY = w->triangles.sizeY
		* w->triangles.offset.y + w->triangles.delta.y + 1;
	sumX1 = (w->triangles.sizeY
		* w->triangles.offset.x + w->triangles.delta.x) / 2;
	sumX2 = sumX1 + (w->triangles.sizeX - 1) * w->triangles.offset.x;
	x -= w->triangles.puzzleOffset.x;
	y -= w->triangles.puzzleOffset.y;
	if (y < 0) /* above */
		return -2;
	if (y > sumY - w->triangles.delta.y) /* below */
		return -3;
	if (x * (sumY + w->triangles.delta.y) + y * (sumX1 + 1) <
			(sumX1 + 1) * (sumY + w->triangles.delta.y)) /* left */
		return -4;
	if (x * (sumY + w->triangles.delta.y) - y * (sumX2 - 1) >
			(sumX2 - 1) * (sumY + w->triangles.delta.y)) /* right */
		return -5;
	*row = (y - w->triangles.delta.y) / w->triangles.offset.y;
	*trbl = (x - sumX1 - 1 + *row * w->triangles.offset.x / 2) /
		w->triangles.offset.x;
	*trbl += ((x - (*trbl + 1) * w->triangles.offset.x) *
		(sumY + w->triangles.delta.y) + y * (sumX1 + 1)) /
		((sumX1 + 1) * (sumY + w->triangles.delta.y));
	*tlbr = (-x + sumX2 - 1 + *row * w->triangles.offset.x / 2) /
		w->triangles.offset.x;
	*tlbr += 1 + ((-x - (*tlbr + 1) * w->triangles.offset.x) *
		(sumY + w->triangles.delta.y) + y * (sumX1 - 1)) /
		((sumX2 - 1) * (sumY + w->triangles.delta.y));
	if (*row >= 0 && *trbl >= 0 && *tlbr >= 0 && *row < w->triangles.sizeY &&
			*tlbr < w->triangles.sizeX + w->triangles.sizeY - 1 &&
			*trbl < w->triangles.sizeX + w->triangles.sizeY - 1)
		return toPosition(w, *row, *trbl, *tlbr);
	else
		return -1;
}

static void
selectTiles(TrianglesWidget w)
{
	if (!w->triangles.corners) {
		int dir = nextNoCornDir(w, w->triangles.currentPosition);

		if (dir != -1) {
#ifdef ANIMATE
			if (w->triangles.delay > 0) {
				animateNoCornSlide(w, NORMAL, True);
#ifdef USE_SOUND
				if (w->triangles.sound) {
					playSound(BUMPSOUND);
				}
#endif
			} else
#endif
			{
				discreteNoCornMoves(w, dir, NORMAL);
			}
		}
	} else if (w->triangles.currentPosition <
			w->triangles.spacePosition[w->triangles.currentPositionOrient]) {
#ifdef ANIMATE
		if (w->triangles.delay > 0) {
			animateCornerSlide(w, DOWN, NORMAL, True);
#ifdef USE_SOUND
			if (w->triangles.sound) {
				playSound(BUMPSOUND);
			}
#endif
		} else
#endif
		{
			while (w->triangles.currentPosition <
					w->triangles.spacePosition[w->triangles.currentPositionOrient]) {

				discreteCornerMoves(w, DOWN, NORMAL);
			}
		}
	} else { /* w->triangles.currentPosition >
			w->triangles.spacePosition[w->triangles.currentPositionOrient] */
#ifdef ANIMATE
		if (w->triangles.delay > 0) {
			animateCornerSlide(w, UP, NORMAL, True);
#ifdef USE_SOUND
			if (w->triangles.sound) {
				playSound(BUMPSOUND);
			}
#endif
		} else
#endif
		{
			while (w->triangles.currentPosition >
					w->triangles.spacePosition[w->triangles.currentPositionOrient]) {
				discreteCornerMoves(w, UP, NORMAL);
			}
		}
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static Boolean isDeadCorner(TrianglesWidget w, int pos) {
	if (!w->triangles.corners) {
		if (w->triangles.tileOfPosition[pos] == 1 ||
				w->triangles.tileOfPosition[pos] == w->triangles.sizeSize - 1 ||
				w->triangles.tileOfPosition[pos] == w->triangles.prevSizeSize)
			return True;
	}
	return False;
}

static void
randomizeTiles(TrianglesWidget w)
{
	if (w->triangles.currentPosition >= 0)
		return;
	w->triangles.cheat = False;
	/* First interchange tiles but only with other tiles
	 * of the same orientation and even number of times */
	if (w->triangles.sizeX > 2) {
		int currentPos, randomPos, randomRow;
		int currentOrient = UP, randomOrient = DOWN;
		int step = 1, fin = 1, count = 0;

		for (currentPos = 0; currentPos < w->triangles.sizeSize; currentPos++) {
			if (isDeadCorner(w, currentPos)) {
				continue;
			}
			randomPos = currentPos;
			while (currentPos == randomPos ||
					currentOrient != randomOrient ||
					isDeadCorner(w, randomPos)) {
				randomPos = NRAND(w->triangles.sizeSize);
				randomRow = toRow(w, randomPos);
				randomOrient = (((randomRow +
					toTrBl(w, randomPos, randomRow) +
					toTlBr(w, randomPos, randomRow))
					& 1) == 1) ? DOWN : UP;
			}
			count += exchangeTiles(w, currentPos, randomPos);
			if (fin == currentPos + 1) {
				currentOrient = UP;
				step += 2;
				fin += step;
			} else
				currentOrient = !currentOrient;
		}
		if ((count & 1) == 1 && !w->triangles.corners) {
			if (!exchangeTiles(w, 1, 3) &&
					!exchangeTiles(w, 5, 7)) {
				DISPLAY_WARNING("randomizeTiles: should not get here");
			}
		}
		drawAllTiles(w);
	}
	/* Now move the spaces around randomly */
	if (w->triangles.sizeX + w->triangles.sizeY > 2) {
		int big = w->triangles.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(TrianglesWidget 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) printf("corrupt record, expecting an integer for corners\n");
		return;
	}
	if (w->triangles.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->triangles.sizeX; i < sizeX; i++) {
			setPuzzle(w, ACTION_INCX);
		}
		for (i = w->triangles.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) printf("corrupt record, expecting an integer for sizeY\n");
		return;
	}
	if (sizeY >= MIN_TILES) {
		for (i = w->triangles.sizeY; i < sizeY; i++) {
			setPuzzle(w, ACTION_INCY);
		}
		for (i = w->triangles.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) printf("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(TrianglesWidget 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->triangles.cheat = True; /* Assume the worst. */
}

static void
writeTiles(TrianglesWidget 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, "w")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "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->triangles.corners) ? 1 : 0);
	(void) fprintf(fp, "sizeX%c %d\n", SYMBOL, w->triangles.sizeX);
	(void) fprintf(fp, "sizeY%c %d\n", SYMBOL, w->triangles.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(TrianglesWidget w)
{
	if (madeMoves(&undo) &&
			w->triangles.currentPosition < 0) {
		int direction;

		getMove(&undo, &direction);
		setMove(&redo, direction);
		direction = (direction + (COORD / 2)) % COORD;
		if (w->triangles.corners && moveCornerDir(w, direction, DOUBLE)) {
			setPuzzle(w, ACTION_UNDO);
		} else if (!w->triangles.corners && moveNoCornDir(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(TrianglesWidget w)
{
	if (madeMoves(&redo) &&
			w->triangles.currentPosition < 0) {
		int direction;

		getMove(&redo, &direction);
		setMove(&undo, direction);
		if (w->triangles.corners && moveCornerDir(w, direction, DOUBLE)) {
			setPuzzle(w, ACTION_REDO);
		} else if (!w->triangles.corners && moveNoCornDir(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(TrianglesWidget w)
{
	if (w->triangles.currentPosition >= 0)
		return;
	resetTiles(w);
	drawAllTiles(w);
	setPuzzle(w, ACTION_RESET);
}

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

static void
cornersTiles(TrianglesWidget w)
{
	setPuzzle(w, ACTION_CORNERS);
}

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

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

static void
soundTiles(TrianglesWidget w)
{
	w->triangles.sound = !w->triangles.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(TrianglesWidget w)
{
	struct tagColor {
		int red, green, blue;
	} color;
	char szBuf[80];

	w->triangles.sizeX = (int) GetPrivateProfileInt(SECTION,
		"sizeX", DEFAULT_TILESX, INIFILE);
	w->triangles.sizeY = (int) GetPrivateProfileInt(SECTION,
		"sizeY", DEFAULT_TILESY, INIFILE);
	w->triangles.corners = (BOOL) GetPrivateProfileInt(SECTION,
		"corners", DEFAULT_CORNERS, INIFILE);
	w->triangles.base = (int) GetPrivateProfileInt(SECTION,
		"base", DEFAULT_BASE, INIFILE);
	w->triangles.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->triangles.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->triangles.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->triangles.tileGC[1] = RGB(color.red, color.green, color.blue);
	w->triangles.tileGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->triangles.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->triangles.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->triangles.inverseGC[1] = RGB(color.red, color.green, color.blue);
	w->triangles.inverseGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->triangles.inverseGC[2] = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	w->triangles.inverseGC[3] = RGB(darker(darker(color.red)),
		darker(darker(color.green)), darker(darker(color.blue)));
	w->triangles.inverseGC[4] = RGB(darker(darker(darker(color.red))),
		darker(darker(darker(color.green))),
		darker(darker(darker(color.blue))));
	(void) GetPrivateProfileString(SECTION,
		"picture", PICTURE, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->triangles.picture, szBuf, 80);
	w->triangles.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", 10, INIFILE);
	w->triangles.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", 0, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"bumpSound", BUMPSOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->triangles.bumpSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->triangles.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->triangles.scoreFile, szBuf, 80);
}

void
destroyPuzzle(TrianglesWidget w, HBRUSH brush)
{
	if (w->core.memDC) {
		if (w->triangles.bufferTiles != NULL) {
			DeleteObject(w->triangles.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(TrianglesWidget 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(TrianglesWidget 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(TrianglesWidget w)
{
	Display *display = XtDisplay(w);
	Window window = XtWindow(w);
	XGCValues values;
	XtGCMask valueMask;

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

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

	checkTiles(w);
	if (w->triangles.font != c->triangles.font ||
			w->triangles.textColor != c->triangles.textColor ||
			w->triangles.reverse != c->triangles.reverse ||
			w->triangles.mono != c->triangles.mono) {
		loadFont(w);
		setAllColors(w);
		redrawTiles = True;
	} else if (w->triangles.background != c->triangles.background ||
			w->triangles.foreground != c->triangles.foreground ||
			w->triangles.textColor != c->triangles.textColor ||
			w->triangles.tileColor != c->triangles.tileColor) {
		setAllColors(w);
		redrawTiles = True;
	}
	if (w->triangles.sizeX != c->triangles.sizeX ||
			w->triangles.sizeY != c->triangles.sizeY ||
			w->triangles.corners != c->triangles.corners ||
			w->triangles.base != c->triangles.base) {
		sizePuzzle(w);
		redraw = True;
	} else if (w->triangles.offset.x != c->triangles.offset.x ||
			w->triangles.offset.y != c->triangles.offset.y) {
		resizePuzzle(w);
		redraw = True;
	}
	if (w->triangles.delay != c->triangles.delay) {
		w->triangles.numSlices = ((w->triangles.delay < MAX_SLICES) ?
			w->triangles.delay + 1 : MAX_SLICES);
	}
	if (w->triangles.menu != ACTION_IGNORE) {
		int menu = w->triangles.menu;

		w->triangles.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);
		if (w->triangles.focus)
			drawFrame(w, 0, True);
		drawAllTiles(w);
	}
	return (redraw);
}

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

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

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

static void
resizeTiles(TrianglesWidget w)
{
	int i, j;

#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->triangles.bufferTiles[i] != NULL) {
			DeleteObject(w->triangles.bufferTiles[i]);
				w->triangles.bufferTiles[i] = NULL;
		}
		if (!(w->triangles.picture && *(w->triangles.picture))) {
			if ((w->triangles.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->triangles.oldColormap == None) {
		w->triangles.mono = (xgwa.depth < 2 || w->triangles.mono);
		w->triangles.oldColormap = xgwa.colormap;
	}
	for (i = 0; i < 2; i++) {
		if (w->triangles.bufferTiles[i] != None) {
			XFreePixmap(display, w->triangles.bufferTiles[i]);
			w->triangles.bufferTiles[i] = None;
		}
		if ((w->triangles.bufferTiles[i] = XCreatePixmap(display,
				window, w->core.width, w->core.height,
				xgwa.depth)) == None) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
#endif
	if (w->triangles.picture && *(w->triangles.picture)) {
#ifdef WINVER
		for (i = 0; i < 2; i++) {
			w->triangles.bufferTiles[i] =
				LoadBitmap(w->core.hInstance,
				w->triangles.picture);
		}
#else
		if (w->triangles.image != NULL) {
			destroyImage(&(w->triangles.image),
				&(w->triangles.graphicsFormat));
		}
		if (!getImage(display, window,
				xgwa.visual, w->triangles.oldColormap, xgwa.depth,
				&(w->triangles.image), w->triangles.picture,
				w->triangles.install, &(w->triangles.graphicsFormat),
				&(w->triangles.colormap))) {
			w->triangles.picture = NULL;
		} else if (w->triangles.image == NULL) {
			w->triangles.picture = NULL;
		}
#endif
	}
#ifndef WINVER
	if (!(w->triangles.picture && *(w->triangles.picture)) &&
			!fixedColors(xgwa.visual, xgwa.depth, w->triangles.install) &&
			w->triangles.colormap == None) {
		w->triangles.colormap = XCreateColormap(display, window,
			xgwa.visual, AllocNone);
	}
	setAllColors(w);
	for (i = 0; i < 2; i++) {
		FILLRECTANGLE(w, w->triangles.bufferTiles[i],
			w->triangles.inverseGC[2],
			0, 0, w->core.width, w->core.height);
		if ((w->triangles.picture && *(w->triangles.picture))) {
			(void) XPutImage(display, w->triangles.bufferTiles[i],
				w->triangles.inverseGC[2], w->triangles.image, 0, 0,
				0, 0,
				MIN(w->triangles.image->width, w->core.width),
				MIN(w->triangles.image->height, w->core.height));
		}
	}
#endif
	for (j = 0; j < MAX_ORIENT; j++)
		for (i = 0; i <= ROW_TYPES; i++) {
			triangleList[j][i].x = (w->triangles.tileSize.x / 2) *
				triangleUnit[j][i].x;
			triangleList[j][i].y = w->triangles.tileSize.y *
				triangleUnit[j][i].y;
		}
	for (i = 0; i <= 6; i++) {
		hexagonList[i].x = (w->triangles.tileSize.x / 6) *
			hexagonUnit[i].x;
		hexagonList[i].y = (w->triangles.tileSize.y / 3) *
			hexagonUnit[i].y;
	}
#ifdef RHOMBUS
	for (j = 0; j < ROW_TYPES; j++)
		for (i = 0; i <= ROW_TYPES + 1; i++) {
			rhombusList[j][i].x = ((w->triangles.tileSize.x + w->triangles.delta.x) / 2 ) *
				rhombusUnit[j][i].x;
			rhombusList[j][i].y = (w->triangles.tileSize.y + w->triangles.delta.y - 1) *
				rhombusUnit[j][i].y;
		}
#endif
	if (!(w->triangles.picture && *(w->triangles.picture))) {
		drawAllTiles(w);
	}
}

#ifndef WINVER
static
#endif
void
resizePuzzle(TrianglesWidget 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->triangles.frameThickness.y = MIN(16,
		MIN(w->core.width, w->core.height) / 16);
	w->triangles.frameThickness.x = (int) (sqrt_3 * w->triangles.frameThickness.y);
	w->triangles.delta.x = 5;
	w->triangles.delta.y = 3;
	w->triangles.offset.x = MAX(((int) w->core.width - 2 * w->triangles.frameThickness.x
		- w->triangles.delta.x)
		/ (w->triangles.sizeX + w->triangles.sizeY - 1), 0);
	w->triangles.offset.y = MAX(((int) w->core.height - 2 * w->triangles.frameThickness.y
		- 2 * w->triangles.delta.y) / w->triangles.sizeY, 0);
	tempSize.x = (int) (w->triangles.offset.y * 2.0 / sqrt_3);
	tempSize.y = (int) (w->triangles.offset.x * sqrt_3 / 2.0);
	if (tempSize.y <= w->triangles.offset.y)
		w->triangles.offset.y = tempSize.y;
	else /* tempSize.x <= w->triangles.offset.x */
		w->triangles.offset.x = tempSize.x;
	w->triangles.puzzleSize.x = w->triangles.offset.x
		* (w->triangles.sizeX + w->triangles.sizeY - 1)
		+ w->triangles.delta.x + 2;
	w->triangles.puzzleSize.y = w->triangles.offset.y
		* w->triangles.sizeY
		+ w->triangles.delta.y + 2;
	w->triangles.puzzleOffset.x = ((int) w->core.width
		- w->triangles.puzzleSize.x + 2) / 2;
	w->triangles.puzzleOffset.y = ((int) w->core.height
		- w->triangles.puzzleSize.y) / 2;
	w->triangles.tileSize.x = MAX(w->triangles.offset.x
		- w->triangles.delta.x, 0);
	w->triangles.tileSize.y = MAX(w->triangles.offset.y
		- w->triangles.delta.y, 0);
}

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

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

	(void) SRAND(getpid());
	w->triangles.bufferTiles[0] = None;
	w->triangles.bufferTiles[1] = None;
	w->triangles.colormap = None;
	w->triangles.oldColormap = None;
	w->triangles.fontInfo = NULL;
	w->triangles.textGC = NULL;
	for (i = 0; i < FG_SHADES; i++)
		w->triangles.tileGC[i] = NULL;
	for (i = 0; i < BG_SHADES; i++)
		w->triangles.inverseGC[i] = NULL;
	w->triangles.image = NULL;
#endif
	w->triangles.focus = False;
	loadFont(w);
	w->triangles.tileOfPosition = NULL;
	checkTiles(w);
	newMoves(&undo);
	newMoves(&redo);
	w->triangles.numSlices = ((w->triangles.delay < MAX_SLICES) ?
		w->triangles.delay + 1 : MAX_SLICES);
	w->triangles.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
TrianglesWidget w
#else
Widget renew, XEvent *event, Region region
#endif
)
{
#ifndef WINVER
	TrianglesWidget w = (TrianglesWidget) renew;

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

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

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

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

#ifndef WINVER
static
#endif
void
releasePuzzle(TrianglesWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	if (w->triangles.currentPosition < 0)
		return;
	drawTile(w, w->triangles.currentPosition,
		w->triangles.currentPositionOrient, True, True, TRUE, 0, 0);
	drawTile(w, w->triangles.currentPosition,
		w->triangles.currentPositionOrient, False, False, FALSE, 0, 0);
	selectTiles(w);
	w->triangles.currentPosition = -1;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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