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

#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 incrementPuzzle(TrianglesWidget w,
	XEvent *event, char **args, int nArgs);
static void decrementPuzzle(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 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 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>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\
 <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>i: Increment()\n\
 <KeyPress>d: Decrement()\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 *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBl", (XtActionProc) movePuzzleBl},
	{(char *) "MoveBr", (XtActionProc) movePuzzleBr},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "ClearMaybe", (XtActionProc) clearWithQueryPuzzle},
	{(char *) "Clear2", (XtActionProc) clearWithDoubleClickPuzzle},
	{(char *) "Get", (XtActionProc) getPuzzle},
	{(char *) "Write", (XtActionProc) writePuzzle},
	{(char *) "Undo", (XtActionProc) undoPuzzle},
	{(char *) "Redo", (XtActionProc) redoPuzzle},
	{(char *) "Clear", (XtActionProc) clearPuzzle},
	{(char *) "Randomize", (XtActionProc) randomizePuzzle},
	{(char *) "Solve", (XtActionProc) solvePuzzle},
	{(char *) "Increment", (XtActionProc) incrementPuzzle},
	{(char *) "Decrement", (XtActionProc) decrementPuzzle},
	{(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) "200"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(TrianglesWidget, core.height),
	 XtRString, (caddr_t) "173"},
	{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*/},
	{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*/},
	{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"},
	{XtNsize, XtCSize, XtRInt, sizeof (int),
	 XtOffset(TrianglesWidget, triangles.size),
	 XtRString, (caddr_t) "4"}, /* DEFAULT_TILES */
	{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 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];
#ifdef RHOMBUS
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.size < MIN_TILES) {
		intCat(&buf1,
			"Number of triangles on an edge out of bounds, use at least ",
			MIN_TILES);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_TILES);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->triangles.size = DEFAULT_TILES;
	}
	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
toPosition(int row, int trbl, int tlbr)
{
	return (row * row + row + trbl - tlbr);
}

static int
toOrient(int row, int trbl, int tlbr)
{
	return ((trbl + tlbr == row) ? UP : DOWN);
}

int
toRow(const int pos)
{
	return SQRT(pos);
}

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

int
toTlBr(const int pos, int const posRow)
{
	return ((posRow * posRow + 2 * posRow - pos) / 2);
}

static int
cartesianX(TrianglesWidget w, int pos, int row)
{
	return (pos - row * row - row + w->triangles.size) *
		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(pos);
		int trbl = toTrBl(pos, row);
		int tlbr = toTlBr(pos, row);
		int offset1, offset2;

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

Boolean
checkSolved(const TrianglesWidget w)
{
	int i;

	for (i = 1; i < w->triangles.sizeSize - 1; i++)
		if (w->triangles.tileOfPosition[i - 1] != i)
			return FALSE;
	return TRUE;
}

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

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[4];
	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
drawShadow(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
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;
	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(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 (blank) {
		POLYGON(w, *dr, tileGC, textGC, triangleList[orient], 3,
			True, False);
	} else {
		if (pressedOffset != 0) {
			drawShadow(w, *dr, w->triangles.tileGC[2],
				-pressedOffset, -pressedOffset,
				triangleList[orient], orient == UP);
		}
		fill3DTriangle(w, *dr, tileGC, w->triangles.tileGC[2],
			w->triangles.tileGC[0],
			triangleList[orient],
			pressedOffset == 0, orient == UP);
	}
	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 +
			2 * w->triangles.delta.y :
			w->triangles.digitOffset.y - w->triangles.tileSize.y +
			- w->triangles.tileSize.y / 7 +
			w->triangles.delta.y - 2;
		DRAWTEXT(w, *dr, textGC,
			dx - digitOffsetX,
			dy + w->triangles.tileSize.y / 2 + digitOffsetY,
			buf, i);
	}
}

void
drawAllTiles(const TrianglesWidget w)
{
	int k, side = UP, fin = 1, step = 1;

	for (k = 0; k < w->triangles.sizeSize; k++) {
		drawTile(w, k, side, (w->triangles.tileOfPosition[k] <= 0),
			(w->triangles.tileOfPosition[k] <= 0), FALSE, 0, 0);
		if (fin == k + 1) {
			side = UP;
			step += 2;
			fin += step;
		} else
			side = (side == UP) ? DOWN : UP;
	}
}

static int
movableTile(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;
}

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

	dy = k * w->triangles.offset.y + w->triangles.delta.y + 2 +
		w->triangles.puzzleOffset.y - 2;
	dx = (pos - k * k - k + w->triangles.size) * 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);
}
#endif

#ifdef RHOMBUS
/* 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
animateSlide(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 = movableTile(w);
	if (rowType < 0) {
		char *buf;

		intCat(&buf, "animateSlide: 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(fillBeginPos);
	fillBeginOrient = toOrient(fillBeginRow,
		toTrBl(fillBeginPos, fillBeginRow),
		toTlBr(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 */
			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 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(rowPos[2]);
		firstOrient = toOrient(firstRow,
			toTrBl(rowPos[2], firstRow),
			toTlBr(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(rowPos[2]);
		fromdx = cartesianX(w, rowPos[2], row);
		fromdy = cartesianY(w, row, firstOrient);
		row = toRow(rowPos[0]);
		todx = cartesianX(w, rowPos[0], row);
		tody = cartesianY(w, row, firstOrient);
		dx = todx - fromdx;
		dy = tody - fromdy;
		i = 0;
		while (!slideDone) {
			for (k = 2; k < numTiles + 2; k++)
				drawTile(w, rowPos[k],
					(firstOrient + k) & 1,
					True, True, FALSE, pixX, pixY);
			slideDone = bresenhamLine(0, 0, dx, dy,
				&pixX, &pixY, &pixNo);
			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++;
		}
		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];
		pos = pos2;
		pos2 = posNext;
	}
	w->triangles.tileOfPosition[fillBeginPos] =
		w->triangles.spacePosition[fillBeginOrient];
	w->triangles.tileOfPosition[fillBeginPos2] =
		w->triangles.spacePosition[fillBeginOrientOp];
	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(fillBeginPos);
	w->triangles.spaceRow[fillBeginOrient][TRBL] = toTrBl(fillBeginPos,
		w->triangles.spaceRow[fillBeginOrient][ROW]);
	w->triangles.spaceRow[fillBeginOrient][TLBR] = toTlBr(fillBeginPos,
		w->triangles.spaceRow[fillBeginOrient][ROW]);
	w->triangles.spaceRow[fillBeginOrientOp][ROW] = toRow(fillBeginPos2);
	w->triangles.spaceRow[fillBeginOrientOp][TRBL] = toTrBl(fillBeginPos2,
		w->triangles.spaceRow[fillBeginOrientOp][ROW]);
	w->triangles.spaceRow[fillBeginOrientOp][TLBR] = toTlBr(fillBeginPos2,
		w->triangles.spaceRow[fillBeginOrientOp][ROW]);
#ifdef RHOMBUS
	drawRhombus(w, w->triangles.spacePosition[UP], rhombusType(w), 1, 1);
#endif
}
#endif

static void
resetTiles(TrianglesWidget w)
{
	int i;

	w->triangles.cw = !w->triangles.cw;
	w->triangles.sizeSize = w->triangles.size * w->triangles.size;
	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 (i = 0; i < w->triangles.sizeSize - 2; i++)
		w->triangles.tileOfPosition[i] = i + 1;
	w->triangles.spacePosition[UP] = w->triangles.sizeSize - 1;
	w->triangles.spaceRow[UP][TRBL] = w->triangles.size - 1; *//* i *//*
	w->triangles.spaceRow[UP][TLBR] = 0; *//* j *//*
	w->triangles.spaceRow[UP][ROW] = w->triangles.size - 1; *//* k *//*
	w->triangles.tileOfPosition[w->triangles.sizeSize - 1] = 0;
	if (w->triangles.size > 1) {
		w->triangles.spacePosition[DOWN] = w->triangles.sizeSize - 2;
		w->triangles.spaceRow[DOWN][TRBL] = w->triangles.size - 2; *//* i *//*
		w->triangles.spaceRow[DOWN][TLBR] = 0; *//* j *//*
		w->triangles.spaceRow[DOWN][ROW] = w->triangles.size - 1; *//* k *//*
		w->triangles.tileOfPosition[w->triangles.sizeSize - 2] = -1;
	}*/
	for (i = 0; i < w->triangles.sizeSize; i++) {
		int row, trbl, tlbr;

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

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
drawFrame(TrianglesWidget w, Pixmap dr, Boolean focus)
{
	int sumX, sumY, sumX2, offsetX, offsetY;
	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];

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

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

static void
moveTiles(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(from);
		w->triangles.currentRow[TRBL] =
			toTrBl(from, w->triangles.currentRow[ROW]);
		w->triangles.currentRow[TLBR] =
			toTlBr(from, w->triangles.currentRow[ROW]);
		w->triangles.currentPositionOrient = toOrient(w->triangles.currentRow[ROW],
			w->triangles.currentRow[TRBL],
			w->triangles.currentRow[TLBR]);
		if (w->triangles.currentPosition <
			w->triangles.spacePosition[w->triangles.currentPositionOrient]) {
			animateSlide(w, DOWN, fast, False);
		} else if (w->triangles.currentPosition >
			w->triangles.spacePosition[w->triangles.currentPositionOrient]) {
			animateSlide(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(from);
		w->triangles.spaceRow[orient][TRBL] =
			toTrBl(from, w->triangles.spaceRow[orient][ROW]);
		w->triangles.spaceRow[orient][TLBR] =
			toTlBr(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 int
moveTilesDir(TrianglesWidget w, int direction, int fast)
{
	int orient;

	switch (direction) {
	case TR:
		if (w->triangles.spaceRow[UP][TRBL] ==
				w->triangles.spaceRow[DOWN][TRBL] &&
				w->triangles.spaceRow[UP][ROW] !=
				w->triangles.size - 1) {
			orient = (w->triangles.spacePosition[UP] + 2 >
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
			moveTiles(w, tileNFromSpace(w, 1, TRBL, orient, DOWN),
				!orient, fast);
			return TRUE;
		}
		break;
	case RIGHT:
		if (w->triangles.spaceRow[UP][ROW] ==
				w->triangles.spaceRow[DOWN][ROW] &&
				w->triangles.spaceRow[UP][TRBL] != 0) {
			orient = (w->triangles.spacePosition[UP] <
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
			moveTiles(w, tileNFromSpace(w, 1, ROW, orient, UP),
				!orient, fast);
			return TRUE;
		}
		break;
	case BR:
		if (w->triangles.spaceRow[UP][TLBR] ==
				w->triangles.spaceRow[DOWN][TLBR] &&
				w->triangles.spaceRow[UP][TRBL] != 0) {
			orient = (w->triangles.spacePosition[UP] <
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
			moveTiles(w, tileNFromSpace(w, 1, TLBR, orient, UP),
				!orient, fast);
			return TRUE;
		}
		break;
	case BL:
		if (w->triangles.spaceRow[UP][TRBL] ==
				w->triangles.spaceRow[DOWN][TRBL] &&
				w->triangles.spaceRow[UP][TLBR] != 0) {
			orient = (w->triangles.spacePosition[UP] + 1 <
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
			moveTiles(w, tileNFromSpace(w, 1, TRBL, orient, UP),
				!orient, fast);
			return TRUE;
		}
		break;
	case LEFT:
		if (w->triangles.spaceRow[UP][ROW] ==
				w->triangles.spaceRow[DOWN][ROW] &&
				w->triangles.spaceRow[UP][TLBR] != 0) {
			orient = (w->triangles.spacePosition[UP] >
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
			moveTiles(w, tileNFromSpace(w, 1, ROW, orient, DOWN),
				!orient, fast);
			return TRUE;
		}
		break;
	case TL:
		if (w->triangles.spaceRow[UP][TLBR] ==
				w->triangles.spaceRow[DOWN][TLBR] &&
				w->triangles.spaceRow[UP][ROW] !=
				w->triangles.size - 1) {
			orient = (w->triangles.spacePosition[UP] >
			w->triangles.spacePosition[DOWN]) ? UP : DOWN;
			moveTiles(w, tileNFromSpace(w, 1, TLBR, orient, DOWN),
				!orient, fast);
			return TRUE;
		}
		break;
	default:
		{
			char *buf;

			intCat(&buf, "moveTilesDir: direction ",
				direction);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
	return FALSE;
}

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

#ifndef WINVER
static
#endif
int
movePuzzle(TrianglesWidget w, const int direction)
{
	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
discreteMoves(TrianglesWidget w, int dir, int fast)
{
	int rowType, orient, next;
	char *buf;

	rowType = movableTile(w);
	if (rowType < 0) {
		intCat(&buf, "discreteMoves: 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;
		moveTiles(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,
					"discreteMoves: 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;
		moveTiles(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,
					"discreteMoves: rowType ",
					rowType);
				DISPLAY_WARNING(buf);
				free(buf);
				return;
			}
		}
	}
}

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

	sumX = w->triangles.size * w->triangles.offset.x + w->triangles.delta.x;
	sumY = w->triangles.size * w->triangles.offset.y + w->triangles.delta.y + 1;
	sumX2 = sumX / 2;
	x -= w->triangles.puzzleOffset.x;
	y -= w->triangles.puzzleOffset.y;
	if (x * (sumY + w->triangles.delta.y) + y * (sumX2 + 1) <
			(sumX2 + 1) * (sumY + w->triangles.delta.y) ||
			x * (sumY + w->triangles.delta.y) - y * (sumX2 - 1) >
			(sumX2 - 1) * (sumY + w->triangles.delta.y) ||
			y > sumY - w->triangles.delta.y)
		return -1;
	*row = (y - w->triangles.delta.y) / w->triangles.offset.y;
	*trbl = (x - sumX2 - 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 * (sumX2 + 1)) /
		((sumX2 + 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 * (sumX2 - 1)) /
		((sumX2 - 1) * (sumY + w->triangles.delta.y));
	if (*row >= 0 && *trbl >= 0 && *tlbr >= 0 &&
			*row < w->triangles.size &&
			*trbl < w->triangles.size &&
			*tlbr < w->triangles.size) {
		return toPosition(*row, *trbl, *tlbr);
	} else
		return -1;
}

static void
selectTiles(TrianglesWidget w)
{
	if (w->triangles.currentPosition <
			w->triangles.spacePosition[w->triangles.currentPositionOrient]) {
#ifdef ANIMATE
		if (w->triangles.delay > 0) {
			animateSlide(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]) {

				discreteMoves(w, DOWN, NORMAL);
			}
		}
	} else { /* w->triangles.currentPosition >
			w->triangles.spacePosition[w->triangles.currentPositionOrient] */
#ifdef ANIMATE
		if (w->triangles.delay > 0) {
			animateSlide(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]) {
				discreteMoves(w, UP, NORMAL);
			}
		}
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

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 */
	if (w->triangles.size > 2) {
		int currentPos, randomPos, randomRow;
		int currentOrient = UP, randomOrient = DOWN;
		int step = 1, fin = 1;

		for (currentPos = 0; currentPos < w->triangles.sizeSize; currentPos++) {
			randomPos = currentPos;
			while (currentPos == randomPos || currentOrient != randomOrient) {
				randomPos = NRAND(w->triangles.sizeSize);
				randomRow = toRow(randomPos);
				randomOrient = (((randomRow +
					toTrBl(randomPos, randomRow) +
					toTlBr(randomPos, randomRow))
					& 1) == 1) ? DOWN : UP;
			}
			(void) exchangeTiles(w, currentPos, randomPos);
			if (fin == currentPos + 1) {
				currentOrient = UP;
				step += 2;
				fin += step;
			} else
				currentOrient = !currentOrient;
		}
		drawAllTiles(w);
	}
	/* Now move the spaces around randomly */
	if (w->triangles.size > 1) {
		int big = w->triangles.sizeSize + NRAND(2);
		int lastDirection = -1;
		int randomDirection;

		setPuzzle(w, ACTION_RESET);

		if (w->triangles.size == 2)
			big *= big;

#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, size, moves;
	char *buf1 = NULL, *buf2 = NULL;

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &size) != 1) {
		(void) printf("corrupt record, expecting an integer for size\n");
		return;
	}
	if (size >= MIN_TILES) {
		for (i = w->triangles.size; i < size; i++) {
			setPuzzle(w, ACTION_INCREMENT);
		}
		for (i = w->triangles.size; i > size; i--) {
			setPuzzle(w, ACTION_DECREMENT);
		}
	} else {
		stringCat(&buf1, name, " corrupted: size ");
		intCat(&buf2, buf1, size);
		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: size %d, moves %d\n", name, size, 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, "size%c %d\n", SYMBOL, w->triangles.size);
	(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 (moveTilesDir(w, direction, DOUBLE)) {
			setPuzzle(w, ACTION_UNDO);
		} else {
			char *buf1, *buf2;

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

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

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

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

static void
clearTiles(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;
	setPuzzle(w, ACTION_SOLVE_MESSAGE);
}

static void
incrementTiles(TrianglesWidget w)
{
	setPuzzle(w, ACTION_INCREMENT);
}

static void
decrementTiles(TrianglesWidget w)
{
	if (w->triangles.size <= MIN_TILES)
		return;
	setPuzzle(w, ACTION_DECREMENT);
}

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

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

static void
soundTiles(TrianglesWidget w)
{
	w->triangles.sound = !w->triangles.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.size = (int) GetPrivateProfileInt(SECTION,
		"size", DEFAULT_TILES, 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);
	/* 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)
{
	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.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.size != c->triangles.size ||
			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_INCREMENT:
			incrementTiles(w);
			break;
		case ACTION_DECREMENT:
			decrementTiles(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;
		}
#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)
{
	double sqrt_3 = 1.73205080756887729352744634150587237;
	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.delta.x = 5;
	w->triangles.delta.y = 3;
	w->triangles.offset.x = MAX(((int) w->core.width -
		w->triangles.delta.x) / w->triangles.size, 0);
	w->triangles.offset.y = MAX(((int) w->core.height -
		2 * w->triangles.delta.y) / w->triangles.size, 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.size +
		w->triangles.delta.x + 2;
	w->triangles.puzzleSize.y = w->triangles.offset.y * w->triangles.size +
		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) / 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;
	w->triangles.cw = (NRAND(2) == 0); /* they take the same # of moves */
	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(row, trbl, tlbr);
		rowType = movableTile(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);
#ifdef HAVE_MOTIF
	else {
		setPuzzle(w, ACTION_CLEAR_QUERY);
	}
#endif
}

static void
clearWithDoubleClickPuzzle(TrianglesWidget w
, XEvent *event, char **args, int nArgs
)
{
#ifdef HAVE_MOTIF
	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
incrementPuzzle(TrianglesWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	incrementTiles(w);
}

#ifndef WINVER
static
#endif
void
decrementPuzzle(TrianglesWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	decrementTiles(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);
}

static void
movePuzzleTr(TrianglesWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, TR);
}

static void
movePuzzleLeft(TrianglesWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, LEFT);
}

static void
movePuzzleRight(TrianglesWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, RIGHT);
}

static void
movePuzzleBl(TrianglesWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, BL);
}

static void
movePuzzleBr(TrianglesWidget w, XEvent *event, char **args, int nArgs)
{
	(void) movePuzzle(w, BR);
}
#endif
