/*
 * @(#)Panex.c
 *
 * Copyright 1996 - 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 Panex */

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

#ifdef WINVER
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "C:/ProgramData/wpuzzles/wpanex.ini"
#endif

#define SECTION "setup"

static const char *pyramidColorString[MAX_STACKS - 1] =
{
	"255 0 255",
	"255 165 0"
};
static char pyramidColorChar[MAX_STACKS - 1] =
{'M', 'O'};
#else
#include "pixmaps/grain_lr.xbm"
#include "pixmaps/grain_tb.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(PanexWidget w, XEvent *event, char **args, int nArgs);
static void destroyPuzzle(Widget old);
static void resizePuzzle(PanexWidget w);
static void sizePuzzle(PanexWidget w);
static void initializePuzzle(Widget request, Widget renew);
static void exposePuzzle(Widget renew, XEvent *event, Region region);
static void hidePuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void selectPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void motionPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void releasePuzzle(PanexWidget w,
	XEvent *event, char **args, int nArgs);
static void clearWithQueryPuzzle(PanexWidget w,
	XEvent *event, char **args, int nArgs);
static void clearWithDoubleClickPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void getPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void writePuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void undoPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void redoPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void clearPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void solvePuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void incrementPuzzle(PanexWidget w,
	XEvent *event, char **args, int nArgs);
static void decrementPuzzle(PanexWidget w,
	XEvent *event, char **args, int nArgs);
static void modePuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void speedUpPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void slowDownPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void toggleSoundPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void enterPuzzle(PanexWidget w, XEvent *event, char **args, int nArgs);
static void leavePuzzle(PanexWidget 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\
 <Btn1Down>: Select()\n\
 <Btn1Motion>: Motion()\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>s: Solve()\n\
 <KeyPress>i: Increment()\n\
 <KeyPress>d: Decrement()\n\
 <KeyPress>m: Mode()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsList[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Motion", (XtActionProc) motionPuzzle},
	{(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 *) "Solve", (XtActionProc) solvePuzzle},
	{(char *) "Speed", (XtActionProc) speedUpPuzzle},
	{(char *) "Slow", (XtActionProc) slowDownPuzzle},
	{(char *) "Sound", (XtActionProc) toggleSoundPuzzle},
	{(char *) "Increment", (XtActionProc) incrementPuzzle},
	{(char *) "Decrement", (XtActionProc) decrementPuzzle},
	{(char *) "Mode", (XtActionProc) modePuzzle},
	{(char *) "Enter", (XtActionProc) enterPuzzle},
	{(char *) "Leave", (XtActionProc) leavePuzzle}
};

static XtResource resources[] =
{
	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
	 XtOffset(PanexWidget, core.width),
	 XtRString, (caddr_t) "400"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(PanexWidget, core.height),
	 XtRString, (caddr_t) "400"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(PanexWidget, panex.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(PanexWidget, panex.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(PanexWidget, panex.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(PanexWidget, panex.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(PanexWidget, panex.frameColor),
	 XtRString, (caddr_t) "wheat4" /*XtDefaultForeground*/},
	{XtNtileColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(PanexWidget, panex.tileColor),
	 XtRString, (caddr_t) "gray75" /*XtDefaultBackground*/},
	{XtNpyramidColor0, XtCLabel, XtRString, sizeof (String),
	 XtOffset(PanexWidget, panex.pyramidName[0]),
	 XtRString, (caddr_t) "magenta"},
	{XtNpyramidColor1, XtCLabel, XtRString, sizeof (String),
	 XtOffset(PanexWidget, panex.pyramidName[1]),
	 XtRString, (caddr_t) "orange"},
	{XtNstippleFrame, XtCStippleFrame, XtRBoolean, sizeof (Boolean),
	 XtOffset(PanexWidget, panex.stippleFrame),
	 XtRString, (caddr_t) "TRUE"},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(PanexWidget, panex.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(PanexWidget, panex.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(PanexWidget, panex.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNdripSound, XtCDripSound, XtRString, sizeof (String),
	 XtOffset(PanexWidget, panex.dripSound),
	 XtRString, (caddr_t) DRIPSOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(PanexWidget, panex.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNtiles, XtCTiles, XtRInt, sizeof (int),
	 XtOffset(PanexWidget, panex.tiles),
	 XtRString, (caddr_t) "5"}, /* DEFAULT_TILES */
	{XtNmode, XtCMode, XtRInt, sizeof (int),
	 XtOffset(PanexWidget, panex.mode),
	 XtRString, (caddr_t) "2"}, /* DEFAULT_MODE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(PanexWidget, panex.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(PanexWidget, panex.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(PanexWidget, panex.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(PanexWidget, panex.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(PanexWidget, panex.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(PanexWidget, panex.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(PanexWidget, panex.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(PanexWidget, panex.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(PanexWidget, panex.select), XtRCallback, (caddr_t) NULL}
};

PanexClassRec panexClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Panex",	/* class name */
		sizeof (PanexRec),	/* 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 panexWidgetClass = (WidgetClass) & panexClassRec;

void
setPuzzle(PanexWidget w, int reason)
{
	panexCallbackStruct cb;

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

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

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

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

static int startPositions[MAX_MODES][MAX_STACKS] =
{
	{0, -1, -1},
	{0, -1, 1},
	{0, -1, 1}
};
static int finishPositions[MAX_MODES][MAX_STACKS] =
{
	{-1, -1, 0},
	{1, -1, 0},
	{1, -1, 0}
};

static Point trapazoidUnit[5] =
{
	{0, 0},
	{1, 1},
	{-3, 0},
	{1, -1},
	{2, 0}
};

#define MULTDELAY 48

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

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

	if (w->panex.tiles < MIN_TILES) {
		intCat(&buf1, "Number of tiles 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->panex.tiles = DEFAULT_TILES;
	}
	if (w->panex.mode < HANOI || w->panex.mode > PANEX) {
		intCat(&buf1, "Mode is in error, use ", HANOI);
		stringCat(&buf2, buf1, " for Hanoi, ");
		free(buf1);
		intCat(&buf1, buf2, ALGORITHME);
		free(buf2);
		stringCat(&buf2, buf1, " for Algorithme, ");
		free(buf1);
		intCat(&buf1, buf2, PANEX);
		free(buf2);
		stringCat(&buf2, buf1, " for Panex, defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_MODE);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->panex.mode = DEFAULT_MODE;
	}
	if (w->panex.delay < 0) {
		intCat(&buf1, "Delay cannot be negative (",
			w->panex.delay);
		stringCat(&buf2, buf1, "), taking absolute value");
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		w->panex.delay = -w->panex.delay;
	}
}

static int
cartesianX(PanexWidget w, int stack)
{
	return stack * w->panex.pos.x + w->panex.delta.x + w->panex.puzzleOffset.x;
}

static int
cartesianY(PanexWidget w, int loc)
{
	return loc * w->panex.pos.y + w->panex.delta.y + w->panex.puzzleOffset.y;
}

#if 0
/* Only applies to Panex */
static int middlePositions[MAX_STACKS] =
{-1, 0, 1};

Boolean
checkMiddle(PanexWidget w)
{
	int stack, loc;
	PanexLoc i;

	for (stack = 0; stack < MAX_STACKS; stack++)
		if ((i.stack = middlePositions[stack]) >= 0)
			for (loc = 0; loc < w->panex.tiles; loc++) {
				i.loc = loc - 1;
				if (w->panex.tileOfPosition[stack][loc + w->panex.blanks].stack != i.stack ||
						w->panex.tileOfPosition[stack][loc + w->panex.blanks].loc != i.loc)
					return False;
			}
	return True;
}
#endif

Boolean
checkSolved(PanexWidget w)
{
	int stack, loc;
	PanexLoc i;

	for (stack = 0; stack < MAX_STACKS; stack++)
		if ((i.stack = finishPositions[w->panex.mode][stack]) >= 0)
			for (loc = 0; loc < w->panex.tiles; loc++) {
				i.loc = loc;
				if (w->panex.tileOfPosition[stack][loc + w->panex.blanks].stack != i.stack ||
						w->panex.tileOfPosition[stack][loc + w->panex.blanks].loc != i.loc)
					return False;
			}
	return True;
}

#ifdef DEBUG
void
printStacks(PanexWidget w)
{
	int stack, position;

	(void) printf("top: where are the tiles in the stack\n");
	for (position = 0; position < w->panex.tiles + w->panex.blanks; position++)
		for (stack = 0; stack < MAX_STACKS; stack++) {
			(void) printf("%d,%d", w->panex.tileOfPosition[stack][position].stack,
				w->panex.tileOfPosition[stack][position].loc);
			if (stack == MAX_STACKS - 1)
				(void) printf("\n");
			else
				(void) printf(" | ");
		}
}

void
printTiles(PanexWidget w)
{
	int stack, position;

	(void) printf("pot: which stack are the tiles in\n");
	for (position = 0; position < w->panex.tiles; position++)
		for (stack = 0; stack < w->panex.stacks; stack++) {
			(void) printf("%d,%d", w->panex.positionOfTile[stack][position].stack,
				w->panex.positionOfTile[stack][position].loc);
			if (stack == w->panex.stacks - 1)
				(void) printf("\n");
			else
				(void) printf(" | ");
		}
}
#endif

static void
fill3DRect(PanexWidget w, Pixmap dr, GC gc, GC darkerGC, GC brighterGC,
		int x, int y, int width, int height, Boolean raised)
{
	GC currentGC = (raised) ? gc : darkerGC;

	if (width > 2 && height > 2) {
		FILLRECTANGLE(w, dr, currentGC,
			x + 1, y + 1, width - 2, height - 2);
	}
	currentGC = (raised) ? brighterGC : darkerGC;
	FILLRECTANGLE(w, dr, currentGC,
		x, y, 1, height);
	if (width > 1) {
		FILLRECTANGLE(w, dr, currentGC,
			x + 1, y, width - 1, 1);
	}
	FILLRECTANGLE(w, dr, currentGC,
		x + 1, y, 1, height);
	if (width > 2) {
		FILLRECTANGLE(w, dr, currentGC,
			x + 2, y + 1, width - 2, 1);
	}
	currentGC = (raised) ? darkerGC : gc;
	if (width > 1 && height > 1) {
		FILLRECTANGLE(w, dr, currentGC,
			x + 1, y + height - 1, width - 1, 1);
		FILLRECTANGLE(w, dr, currentGC,
			x + width - 1, y, 1, height - 1);
	}
	if (width > 3 && height > 2) {
		FILLRECTANGLE(w, dr, currentGC,
			x + 2, y + height - 2, width - 3, 1);
	}
	if (width > 2 && height > 3) {
		FILLRECTANGLE(w, dr, currentGC,
			x + width - 2, y + 1, 1, height - 3);
	}
}

static void
drawShadow(PanexWidget w, Pixmap dr, GC gc, int startX, int startY,
		int sizeX, int sizeY)
{
	FILLRECTANGLE(w, dr, gc, startX, startY, sizeX, 1);
	FILLRECTANGLE(w, dr, gc, startX, startY, 1, sizeY);
}

static void
drawPyramid(PanexWidget w, Pixmap dr, int color, int i, int j, int size,
		int offsetPosition)
{
	Point trapazoidList[5];
	int k;
	GC pyramidGC;

	for (k = 0; k <= 4; k++) {
		if (ABS(trapazoidUnit[k].x) == 3)
			trapazoidList[k].x = (size + 1) *
				SIGN(trapazoidUnit[k].x) *
				w->panex.tileSize.x / (w->panex.tiles + 1);
		else if (ABS(trapazoidUnit[k].x) == 2)
			trapazoidList[k].x = size * SIGN(trapazoidUnit[k].x) *
				w->panex.tileSize.x / (w->panex.tiles + 1);
		else
			trapazoidList[k].x = w->panex.tileSize.x /
				(2 * (w->panex.tiles + 1));
		trapazoidList[k].y = (w->panex.tileSize.y - 5) *
			trapazoidUnit[k].y;
	}
	k = w->panex.delta.x + i * w->panex.pos.x + w->panex.puzzleOffset.x +
		w->panex.tileSize.x / 2 + offsetPosition + 1;
	trapazoidList[0].x = trapazoidList[4].x / 2 + k;
	trapazoidList[0].y = j * w->panex.pos.y + w->panex.delta.y +
		w->panex.puzzleOffset.y + offsetPosition + 2;
	if (w->panex.mono) {
		pyramidGC = w->panex.inverseGC[1];
	} else {
#ifndef WINVER
		if ((w->panex.pyramidColor[color] == w->panex.borderColor)
				&& offsetPosition)
			pyramidGC = w->panex.tileGC[1];
		else if ((w->panex.pyramidColor[color] == w->panex.tileColor)
				&& !offsetPosition)
			pyramidGC = w->panex.inverseGC[4];
		else
#endif
			pyramidGC = w->panex.pyramidGC[color];

	}
	POLYGON(w, dr, pyramidGC, pyramidGC, trapazoidList, 4, True, False);
	if (w->panex.mono) {

		char buf[2];

		buf[0] =
#ifdef WINVER
			w->panex.pyramidChar[color];
#else
			w->panex.pyramidName[color][0];
#endif
		DRAWTEXT(w, dr, (offsetPosition) ? w->panex.inverseGC[4] :
			w->panex.tileGC[1],
			k - w->panex.letterOffset.x, trapazoidList[0].y +
			w->panex.tileSize.y / 2 + w->panex.letterOffset.y, buf, 1);
	}
}

static void
fillRectClip(PanexWidget w, Pixmap dr, GC gc, int dx, int dy, int sx, int sy,
		int wdx, int wdy, int wsx, int wsy)
{
	int ndx = wdx, nsx = wsx, ndy = wdy, nsy = wsy;

	/* w is the clipping window */
	if (dx + sx < wdx || dx > wdx + wsx ||
			dy + sy < wdy || dy > wdy + wsy ||
			sx <= 0 || sy <= 0 || wsx <= 0 || wsy <= 0) {
		return;
	}
	if (dx > wdx) {
		ndx = dx;
		nsx = wsx - dx + wdx;
	}
	if (dy > wdy) {
		ndy = dy;
		nsy = wsy - dy + wdy;
	}
	if (ndx + nsx > dx + sx) {
		nsx = sx + dx - ndx;
	}
	if (ndy + nsy > dy + sy) {
		nsy = sy + dy - ndy;
	}
	FILLRECTANGLE(w, dr, gc, ndx, ndy, nsx, nsy);
}

static void
drawSlot(PanexWidget w, int stack, int x, int y, int width, int height)
{
	Pixmap dr = 0;
	GC gc = w->panex.inverseGC[1];
	Boolean algorithme = (w->panex.mode == ALGORITHME);
	Boolean panex = (w->panex.mode == PANEX);
	int sizex = w->panex.tileSize.x / 3;
	int sizey = w->panex.tileSize.y / 3;

	FILLRECTANGLE(w, dr, gc, x, y, width, height);
	fillRectClip(w, dr, w->panex.inverseGC[4],
		((algorithme) ? 0 : (sizex + w->panex.delta.x +
			w->panex.puzzleOffset.x)),
		w->panex.delta.y / 2 + w->panex.puzzleOffset.y + sizey,
		((algorithme) ? w->panex.width :
			(sizex + (MAX_STACKS - 1) * w->panex.pos.x)),
		sizey,
		x, y, width, height);
	if (panex || w->panex.tiles != 1) {
		fillRectClip(w, dr, w->panex.inverseGC[4],
			sizex + w->panex.delta.x +
			w->panex.puzzleOffset.x + stack * w->panex.pos.x,
			w->panex.delta.y / 2 + w->panex.puzzleOffset.y + sizey,
			sizex,
			(w->panex.tiles + w->panex.blanks) * w->panex.pos.y + w->panex.delta.y -
			1 - 2 * sizey,
			x, y, width, height);
		fillRectClip(w, dr, w->panex.inverseGC[4],
			sizex + w->panex.delta.x +
			w->panex.puzzleOffset.x + w->panex.pos.x,
			w->panex.delta.y / 2 + w->panex.puzzleOffset.y + sizey,
			sizex,
			(w->panex.tiles + w->panex.blanks) * w->panex.pos.y + w->panex.delta.y -
			1 - 2 * sizey,
			x, y, width, height);
	}
}

static void
drawTile(PanexWidget w, int i, int j, Boolean erase, int pressedOffset,
		int offsetX, int offsetY)
{
	int dx, dy, sx, sy;
	int tileI = w->panex.tileOfPosition[i][j].stack;
	int tileJ = w->panex.tileOfPosition[i][j].loc;
	Boolean algorithme = (w->panex.mode == ALGORITHME);
	Boolean panex = (w->panex.mode == PANEX);
	Pixmap dr = 0;

	if (i < 0 || j < 0) {
		(void) printf("drawTile: i = %d, j = %d\n", i, j);
		return;
	}
	dx = cartesianX(w, i) + pressedOffset + offsetX;
	dy = cartesianY(w, j) + pressedOffset + offsetY;
	sx = cartesianX(w, tileI);
	sy = cartesianY(w, tileJ);
	if (erase) {
		/* Draw Slots */
		FILLRECTANGLE(w, dr, w->panex.inverseGC[1],
			dx, dy,
			w->panex.tileSize.x + 1,
			w->panex.tileSize.y + 1 - pressedOffset);
		if (j == 0) {
			if (!algorithme && i == 0) {
				FILLRECTANGLE(w, dr, w->panex.inverseGC[4],
					dx + w->panex.tileSize.x / 3,
					dy + w->panex.tileSize.y / 3 - 1 -
					pressedOffset,
					2 * w->panex.tileSize.x / 3 + 2,
					w->panex.tileSize.y / 3);
			} else if (!algorithme && i == MAX_STACKS - 1) {
				FILLRECTANGLE(w, dr, w->panex.inverseGC[4],
					dx,
					dy + w->panex.tileSize.y / 3 - 1 -
					pressedOffset,
					2 * w->panex.tileSize.x / 3 - 1,
					w->panex.tileSize.y / 3 +
					pressedOffset);
			} else {
				FILLRECTANGLE(w, dr, w->panex.inverseGC[4],
					dx,
					dy + w->panex.tileSize.y / 3 - 1 -
					pressedOffset,
					w->panex.tileSize.x + 1,
					w->panex.tileSize.y / 3);
			}
			if (panex || w->panex.tiles != 1) {
				FILLRECTANGLE(w, dr, w->panex.inverseGC[4],
					dx + w->panex.tileSize.x / 3 - pressedOffset,
					dy + w->panex.tileSize.y / 3 - 1,
					w->panex.tileSize.x / 3,
					w->panex.tileSize.y - w->panex.tileSize.y / 3 +
					2 - pressedOffset);
			}
		} else if (j == w->panex.tiles + w->panex.blanks - 1) {
			FILLRECTANGLE(w, dr, w->panex.inverseGC[4],
				dx + w->panex.tileSize.x / 3 - pressedOffset,
				dy - pressedOffset,
				w->panex.tileSize.x / 3,
				2 * w->panex.tileSize.y / 3 + 2);
		} else {
			FILLRECTANGLE(w, dr, w->panex.inverseGC[4],
				dx + w->panex.tileSize.x / 3 - pressedOffset,
				dy - pressedOffset,
				w->panex.tileSize.x / 3,
				w->panex.tileSize.y + pressedOffset + 1);
		}
		return;
	}
#ifdef WINVER
	w->core.hOldBitmap = (HBITMAP) SelectObject(w->core.memDC,
		w->panex.bufferTiles[pressedOffset]);
	BitBlt(w->core.hDC,
		dx, dy,
		w->panex.tileSize.x, w->panex.tileSize.y,
		w->core.memDC,
		sx, sy,
		SRCCOPY);
	SelectObject(w->core.memDC, w->core.hOldBitmap);
#else
	XSetGraphicsExposures(XtDisplay(w), w->panex.tileGC[1], False);
	XCopyArea(XtDisplay(w),
		w->panex.bufferTiles[pressedOffset],
		XtWindow(w),
		w->panex.tileGC[1],
		sx, sy,
		w->panex.tileSize.x, w->panex.tileSize.y,
		dx, dy);
#endif
}


static void
drawBufferedTile(PanexWidget w, int i, int j, int pressedOffset)
{
	Pixmap *dr;
	int dx, dy;
	GC tileGC;

	dr = &(w->panex.bufferTiles[pressedOffset]);
	tileGC = w->panex.tileGC[1];
	dx = cartesianX(w, i) + pressedOffset;
	dy = cartesianY(w, j) + pressedOffset;
	if (pressedOffset != 0) {
		drawShadow(w, *dr, w->panex.tileGC[2],
			dx - pressedOffset, dy - pressedOffset,
			w->panex.tileSize.x, w->panex.tileSize.y);
	}
	fill3DRect(w, *dr, tileGC,
		w->panex.tileGC[2], w->panex.tileGC[0],
		dx, dy, w->panex.tileSize.x, w->panex.tileSize.y,
		pressedOffset == 0);
	if (pressedOffset != 0) {
		FILLRECTANGLE(w, *dr, w->panex.tileGC[1],
			dx + w->panex.tileSize.x - 2, dy - 1, 1, 3);
		FILLRECTANGLE(w, *dr, w->panex.tileGC[1],
			dx - 1, dy + w->panex.tileSize.y - 2, 3, 1);
	}
	drawPyramid(w, *dr,
		i, i, j, j, pressedOffset);
}

void
drawAllTiles(PanexWidget w)
{
	int i, j;

	for (i = 0; i < w->panex.stacks; i++)
		for (j = 0; j < w->panex.tiles; j++)
			drawTile(w,
				w->panex.positionOfTile[i][j].stack,
				w->panex.positionOfTile[i][j].loc,
				False, FALSE, 0, 0);
}

static void
drawAllBufferedTiles(const PanexWidget w)
{
	int i, j, l;

	for (i = 0; i < w->panex.stacks; i++)
		for (j = 0; j < w->panex.tiles; j++)
			for (l = 0; l < 2; l++)
				drawBufferedTile(w, i, j, l);
}

static void
animateSlide(PanexWidget w, int currentStack, int currentPosition,
		int nextStack, int nextPosition, int fast)
{
	int diffStack = nextStack - currentStack;
	int diffPosition = nextPosition - currentPosition;
	int dir = 0, spaces = 0, inc;
	int gapI = 0, moveI = 0, space;
	int dx, dy;
	int ix = 0, iy = 0;

	if (diffPosition > 0) {
		dir = BOTTOM;
		spaces = diffPosition;
		moveI = w->panex.pos.y;
	} else if (diffPosition < 0) {
		dir = TOP;
		spaces = -diffPosition;
		moveI = w->panex.pos.y;
	} else if (diffStack > 0) {
		dir = RIGHT;
		spaces = diffStack;
		moveI = w->panex.pos.x;
	} else if (diffStack < 0) {
		dir = LEFT;
		spaces = -diffStack;
		moveI = w->panex.pos.x;
	}
		space = spaces * moveI;
		if ((dir == RIGHT) || (dir == LEFT)) {
			gapI = w->panex.pos.x * fast / w->panex.numSlices;
		} else if ((dir == TOP) || (dir == BOTTOM)) {
			gapI = w->panex.pos.y * fast / w->panex.numSlices;
		}
		if (gapI == 0)
			gapI++;
		FLUSH(w);
		initTimer(w->panex.oldTime);
		for (inc = 0; inc < space + gapI; inc += gapI) {
			if (inc > space) {
				inc = space;
			}
			/* Calculate deltas */
			dx = cartesianX(w, currentStack);
			dy = cartesianY(w, currentPosition);

			if ((dir == RIGHT) || (dir == LEFT)) {
				ix = ((dir == RIGHT) ? inc : -inc);
				iy = 0;
				drawSlot(w, currentStack,
					dx, dy + w->panex.tileSize.y,
					w->panex.tileSize.x + 1, 1);
			} else if ((dir == TOP) || (dir == BOTTOM)) {
				ix = 0;
				iy = ((dir == BOTTOM) ? inc : -inc);
			}
			drawTile(w, currentStack, currentPosition, False,
				FALSE, ix, iy);
			/* Erase old slivers */
			ix += dx;
			iy += dy;
			if (inc != 0)
			switch (dir) {
			case TOP:
				drawSlot(w, currentStack,
					ix, iy + w->panex.tileSize.y,
					w->panex.tileSize.x + 1, gapI);
				FLUSH(w);
				useTimer(&(w->panex.oldTime), w->panex.delay);
				break;
			case RIGHT:
				drawSlot(w, currentStack,
					ix - gapI, iy,
					gapI, w->panex.tileSize.y + 1);
				FLUSH(w);
				useTimer(&(w->panex.oldTime), 3 * w->panex.delay);
				break;
			case BOTTOM:
				drawSlot(w, currentStack,
					ix, iy - gapI,
					w->panex.tileSize.x + 1, gapI);
				FLUSH(w);
				useTimer(&(w->panex.oldTime), w->panex.delay);
				break;
			case LEFT:
				drawSlot(w, currentStack,
					ix + w->panex.tileSize.x, iy,
					gapI + 1, w->panex.tileSize.y + 1);
				FLUSH(w);
				useTimer(&(w->panex.oldTime), 3 * w->panex.delay);
				break;
			}
		}
}

static void
drawSlots(PanexWidget w, Pixmap dr)
{
	int i, dx, dy, y, sizex, sizey;
	Boolean algorithme = (w->panex.mode == ALGORITHME);
	Boolean panex = (w->panex.mode == PANEX);

	sizex = w->panex.tileSize.x / 3;
	sizey = w->panex.tileSize.y / 3;
	if (sizey == 0)
		sizey = 1;
	dx = sizex + w->panex.delta.x + w->panex.puzzleOffset.x;
	y = (w->panex.tiles + w->panex.blanks) * w->panex.pos.y +
		w->panex.delta.y;
	dy = w->panex.delta.y / 2 + w->panex.puzzleOffset.y + sizey;
	FILLRECTANGLE(w, dr, w->panex.inverseGC[4],
		((algorithme) ? w->panex.frameThickness : dx), dy,
		((algorithme) ? w->panex.width :
			(sizex + (MAX_STACKS - 1) * w->panex.pos.x)),
		sizey);
	if (panex || w->panex.tiles != 1)
		for (i = 0; i < MAX_STACKS; i++) {
			FILLRECTANGLE(w, dr, w->panex.inverseGC[4], dx, dy,
				sizex, y - 1 - 2 * sizey);
			dx += w->panex.pos.x;
		}
}

static void
draw3DFrame(const PanexWidget w, Pixmap dr, Boolean inside,
	int x, int y, int width, int height)
{
	GC gc;

	if (inside)
		gc = w->panex.inverseGC[2];
	else
		gc = w->panex.inverseGC[1];
	FILLRECTANGLE(w, dr, gc,
		x, y, width, 1);
	FILLRECTANGLE(w, dr, gc,
		x, y + 1, 1, height - 1);
	FILLRECTANGLE(w, dr, gc,
		x + 1, y + 1, width - 2, 1);
	FILLRECTANGLE(w, dr, gc,
		x + 1, y + 2, 1, height - 3);
	if (inside)
		gc = w->panex.inverseGC[0];
	else
		gc = w->panex.inverseGC[1];
	FILLRECTANGLE(w, dr, gc,
		x + 1, y + height - 1, width - 1, 1);
	FILLRECTANGLE(w, dr, gc,
		x + width - 1, y + 1, 1, height - 2);
	FILLRECTANGLE(w, dr, gc,
		x + 2, y + height - 2, width - 3, 1);
	FILLRECTANGLE(w, dr, gc,
		x + width - 2, y + 2, 1, height - 4);
}

static void
eraseFrame(PanexWidget w, Pixmap dr, Boolean focus)
{
	FILLRECTANGLE(w, dr, w->panex.inverseGC[1],
		1, 1, w->core.width - 2, w->core.height - 2);
	draw3DFrame(w, dr, focus,
		w->panex.frameThickness, w->panex.frameThickness,
		w->core.width - 2 * w->panex.frameThickness,
		w->core.height - 2 * w->panex.frameThickness);
}

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

static void
drawFrameTB(const PanexWidget w, Pixmap dr)
{
	GC gc = w->panex.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->panex.stippleFrame && w->panex.grainTB != (Pixmap) NULL) {
		XSetStipple(display, w->panex.frameGC, w->panex.grainTB);
		XSetFillStyle(display, w->panex.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->panex.frameGC, FillSolid);
#endif
	/* Left */
	/*FILLRECTANGLE(w, dr, gc,
		1, 1, w->panex.frameThickness, w->core.height - 2);*/
	tempList[0].x = w->panex.frameThickness;
	tempList[0].y = w->core.height - w->panex.frameThickness - 1;
	tempList[1].x = 0;
	tempList[1].y = w->core.height - 1;
	tempList[2].x = 0;
	tempList[2].y = 0;
	tempList[3].x = w->panex.frameThickness;
	tempList[3].y = w->panex.frameThickness;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
#if 0
	XGCValues values;
	XtGCMask valueMask;
	valueMask = GCTileStipXOrigin | GCTileStipYOrigin;
	values.ts_x_origin = w->core.width;
	values.ts_y_origin = w->core.height;
	w->panex.frameGC = XtGetGC((Widget) w, valueMask, &values);
#endif
	/* Right */
	/*FILLRECTANGLE(w, dr, gc,
		w->core.width - w->panex.frameThickness - 1, 1,
		w->panex.frameThickness, w->core.height - 2);*/
	tempList[0].x = w->core.width - w->panex.frameThickness - 1;
	tempList[0].y = w->panex.frameThickness;
	tempList[1].x = w->core.width - 1;
	tempList[1].y = 1;
	tempList[2].x = w->core.width - 1;
	tempList[2].y = w->core.height - 1;
	tempList[3].x = w->core.width - w->panex.frameThickness - 1;
	tempList[3].y = w->core.height - w->panex.frameThickness - 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
#if 0
	values.ts_x_origin = 0;
	values.ts_y_origin = 0;
	w->panex.frameGC = XtGetGC((Widget) w, valueMask, &values);
#endif
}

static void
drawFrame(PanexWidget w, Pixmap dr, Boolean focus)
{
	draw3DFrame(w, dr, focus,
		w->panex.frameThickness, w->panex.frameThickness,
		w->core.width - 2 * w->panex.frameThickness,
		w->core.height - 2 * w->panex.frameThickness);
	drawFrameTB(w, dr);
	drawFrameLR(w, dr);
}

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

int
topOfStack(PanexWidget w, int stack, int start)
{
	int i;

	for (i = start; i < w->panex.tiles + w->panex.blanks; i++)
		if (w->panex.tileOfPosition[stack][i].stack >= 0)
			return i;
	return -1;
}

static Boolean
jumpMove(PanexWidget w, int fromStack, int toStack)
{
	return (((w->panex.mode == ALGORITHME) &&
		(w->panex.tileOfPosition[1][0].stack >= 0) &&
		((toStack == 0 && fromStack == 2) ||
		(toStack == 2 && fromStack == 0))));
}

static int
requestMove(PanexWidget w, int fromStack, int fromPosition, int toStack,
		Boolean limbo)
{
	int i;

	/* Do not have to check above stack since it is the top one */
	if (toStack > fromStack) {
		if (!jumpMove(w, fromStack, toStack)) {
			for (i = fromStack + 1; i <= toStack; i++) {
				if (w->panex.tileOfPosition[i][0].stack >= 0)
					return (-1);
			}
		}
		i = topOfStack(w, toStack, 0);
	} else if (toStack < fromStack) {
		if (!jumpMove(w, fromStack, toStack)) {
			for (i = fromStack - 1; i >= toStack; i--) {
				if (w->panex.tileOfPosition[i][0].stack >= 0)
					return (-1);
			}
		}
		i = topOfStack(w, toStack, 0);
	} else {
		/* Ruled out toStack == fromStack, except for motion */
		i = topOfStack(w, toStack, 1);
	}
	i = (i == -1) ? w->panex.tiles + w->panex.blanks - 1 : i - 1;
	if (w->panex.mode == HANOI) {
		if (i == w->panex.tiles + w->panex.blanks - 1 || limbo ||
				w->panex.tileOfPosition[toStack][i + 1].loc >=
				w->panex.tileOfPosition[fromStack][fromPosition].loc)
			return i;
		else
			return -2;
	} else if (w->panex.mode == PANEX) {
		if (i > w->panex.tileOfPosition[fromStack][fromPosition].loc + 1)
			return (w->panex.tileOfPosition[fromStack][fromPosition].loc + 1);
		else
			return i;
	} else {
		if (i == w->panex.tiles + w->panex.blanks - 1 || limbo ||
				w->panex.tileOfPosition[toStack][i + 1].loc >=
				w->panex.tileOfPosition[fromStack][fromPosition].loc)
			return i;
		else
			return -2;
	}
}

static int
selectTile(PanexWidget w, int positionX)
{
	int x = positionX - w->panex.puzzleOffset.x;
	int i = (x - w->panex.delta.x / 2) / w->panex.pos.x;

	if (i < 0)
		i = 0;
	else if (i >= MAX_STACKS)
		i = MAX_STACKS - 1;
#if 0
	y -= w->panex.puzzleOffset.y;
	j = (y - w->panex.delta.y / 2) / w->panex.pos.y;
	if (j < 0)
		j = 0;
	else if (j > w->panex.tiles)
		j = w->panex.tiles;
#endif
	return i;
}

static void
virtualMove(PanexWidget w, int currentStack, int currentPosition,
		int nextStack, int nextPosition)
{
	PanexLoc top;

	top = w->panex.tileOfPosition[currentStack][currentPosition];
	w->panex.tileOfPosition[nextStack][nextPosition] = top;
	w->panex.positionOfTile[top.stack][top.loc].stack = nextStack;
	w->panex.positionOfTile[top.stack][top.loc].loc = nextPosition;
	w->panex.tileOfPosition[currentStack][currentPosition].stack = -1;
}

static void
discreteMoves(PanexWidget w, int currentStack, int currentPosition,
		int nextStack, int nextPosition)
{
	drawTile(w, currentStack, currentPosition, True, FALSE, 0, 0);
	virtualMove(w, currentStack, currentPosition, nextStack, nextPosition);
	drawTile(w, nextStack, nextPosition, False, FALSE, 0, 0);
	FLUSH(w);
	Sleep((unsigned int) (MULTDELAY * w->panex.delay /
		(w->panex.tiles + MAX_STACKS - 1)));
}

static void
slideTile(PanexWidget w, int fromStack, int fromPosition,
		int toStack, int toPosition, Boolean jump, int fast)
{
	int currentStack = fromStack, currentPosition = fromPosition;
	int nextStack = fromStack, nextPosition = fromPosition;

	if (currentPosition > 0) {
		if (fast != INSTANT && w->panex.delay > 0) {
			nextPosition = 0;
			animateSlide(w, currentStack, currentPosition,
				nextStack, nextPosition, fast);
			virtualMove(w, currentStack, currentPosition,
				nextStack, nextPosition);
			currentPosition = nextPosition;
		} else {
			while (currentPosition > 0) {
				nextPosition = currentPosition - 1;
				discreteMoves(w, currentStack, currentPosition,
					nextStack, nextPosition);
				currentPosition = nextPosition;
			}
		}
#ifdef USE_SOUND
		if (w->panex.sound) {
			playSound(MOVESOUND);
		}
#endif
	}
	if (currentStack != toStack) {
		if (fast != INSTANT && w->panex.delay > 0 && !jump) {
			nextStack = toStack;
			animateSlide(w, currentStack, currentPosition,
				nextStack, nextPosition, fast);
			virtualMove(w, currentStack, currentPosition,
				nextStack, nextPosition);
			currentStack = nextStack;
		} else {
			while (currentStack != toStack) {
				if (jump)
					nextStack = toStack;
				else
					nextStack = (currentStack < toStack) ?
						currentStack + 1 : currentStack - 1;
				nextPosition = currentPosition;
				discreteMoves(w, currentStack, currentPosition,
					nextStack, nextPosition);
				currentStack = nextStack;
			}
		}
#ifdef USE_SOUND
		if (w->panex.sound) {
			if (jump)
				playSound(DRIPSOUND);
			else
				playSound(MOVESOUND);
		}
#endif
	}
	if (currentPosition < toPosition) {
		if (fast != INSTANT && w->panex.delay > 0) {
			nextPosition = toPosition;
			animateSlide(w, currentStack, currentPosition,
				nextStack, nextPosition, fast);
			virtualMove(w, currentStack, currentPosition,
				nextStack, nextPosition);
			currentPosition = nextPosition;
		} else {
			while (currentPosition < toPosition) {
				nextPosition = currentPosition + 1;
				discreteMoves(w, currentStack, currentPosition,
					nextStack, nextPosition);
				currentPosition = nextPosition;
			}
		}
#ifdef USE_SOUND
		if (w->panex.sound) {
			playSound(MOVESOUND);
		}
#endif
	}
}

static int
moveTile(PanexWidget w, int fromStack, int fromPosition, int toStack, int fast)
{
	int toPosition;

	if ((toPosition = requestMove(w, fromStack, fromPosition, toStack,
			False)) >= 0)
		slideTile(w, fromStack, fromPosition, toStack, toPosition,
			jumpMove(w, fromStack, toStack), fast);
	return toPosition;
}

static Boolean
moveTileToLimbo(PanexWidget w, int fromStack, int fromPosition, int toStack,
		Boolean limbo)
{
	Boolean aMove;

	aMove = (requestMove(w, fromStack, fromPosition, toStack, limbo) >= 0);
	if (aMove)
		slideTile(w, fromStack, fromPosition, toStack, 0,
			jumpMove(w, fromStack, toStack), NORMAL);
	return aMove;
}

static void
resetTiles(PanexWidget w)
{
	int stack, loc;
	PanexLoc i;

	w->panex.stacks = ((w->panex.mode == HANOI) ? 1 : 2);
	if (w->panex.mode == ALGORITHME) {
		if (w->panex.tiles == 1)
			w->panex.blanks = 0;
		else if (w->panex.tiles > 1)
			w->panex.blanks = w->panex.tiles - 2;
	} else if (w->panex.mode == HANOI)
		w->panex.blanks = 0;
	else
		w->panex.blanks = 1;
	w->panex.currentStack = -1;
	for (stack = 0; stack < w->panex.stacks; stack++) {
		if (w->panex.positionOfTile[stack]) {
			free(w->panex.positionOfTile[stack]);
			w->panex.positionOfTile[stack] = NULL;
		}
		if (startLoc[stack]) {
			free(startLoc[stack]);
			startLoc[stack] = NULL;
		}
	}
	for (stack = 0; stack < w->panex.stacks; stack++) {
		if (!(w->panex.positionOfTile[stack] = (PanexLoc *)
				malloc(sizeof (PanexLoc) *
					(size_t) w->panex.tiles))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		if (!(startLoc[stack] = (PanexLoc *)
				malloc(sizeof (PanexLoc) *
					(size_t) w->panex.tiles))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	for (stack = 0; stack < MAX_STACKS; stack++) {
		if (w->panex.tileOfPosition[stack])
			free(w->panex.tileOfPosition[stack]);
		if (!(w->panex.tileOfPosition[stack] = (PanexLoc *)
				malloc(sizeof (PanexLoc) *
					(size_t) (w->panex.tiles + w->panex.blanks)))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	for (stack = 0; stack < w->panex.stacks; stack++) {
		i.stack = 2 * stack;
		for (loc = 0; loc < w->panex.tiles; loc++) {
			i.loc = loc + w->panex.blanks;
			w->panex.positionOfTile[stack][loc] = i;
		}
	}
	for (stack = 0; stack < MAX_STACKS; stack++) {
		i.stack = -1;
		i.loc = -1;
		for (loc = 0; loc < w->panex.blanks; loc++) {
			w->panex.tileOfPosition[stack][loc] = i;
		}
		if ((i.stack = startPositions[w->panex.mode][stack]) >= 0) {
			for (loc = 0; loc < w->panex.tiles; loc++) {
				i.loc = loc;
				w->panex.tileOfPosition[stack][loc + w->panex.blanks] = i;
			}
		} else {
			for (loc = 0; loc < w->panex.tiles; loc++) {
				w->panex.tileOfPosition[stack][loc + w->panex.blanks] = i;
			}
		}
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->panex.started = False;
	w->panex.cheat = False;
}

static void
readFile(PanexWidget w, FILE *fp, char *name)
{
	int c, i, tiles, mode, moves;
	char *buf1 = NULL, *buf2 = NULL;

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &tiles) != 1) {
		(void) printf("corrupt record, expecting an integer for tiles\n");
		return;
	}
	if (tiles >= MIN_TILES) {
		for (i = w->panex.tiles; i < tiles; i++) {
			setPuzzle(w, ACTION_INCREMENT);
		}
		for (i = w->panex.tiles; i > tiles; i--) {
			setPuzzle(w, ACTION_DECREMENT);
		}
	} else {
		stringCat(&buf1, name, " corrupted: tiles ");
		intCat(&buf2, buf1, tiles);
		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", &mode) != 1) {
		(void) printf("corrupt record, expecting an integer for mode\n");
		return;
	}
	if (mode >= HANOI && mode <= PANEX) {
		if (w->panex.mode != mode) {
			setPuzzle(w, ACTION_MODE);
		}
		if (w->panex.mode != mode) {
			setPuzzle(w, ACTION_MODE);
		}
	} else {
		stringCat(&buf1, name, " corrupted: mode ");
		intCat(&buf2, buf1, mode);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, HANOI);
		free(buf1);
		stringCat(&buf1, buf2, " and ");
		free(buf2);
		intCat(&buf2, buf1, PANEX);
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		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;
	if (mode == HANOI)
		(void) printf("%s: mode hanoi, tiles %d, moves %d\n",
			name, tiles, moves);
	else if (mode == ALGORITHME)
		(void) printf("%s: mode algorithme, tiles %d, moves %d\n",
			name, tiles, moves);
	else
		(void) printf("%s: mode panex, tiles %d, moves %d\n",
			name, tiles, moves);
}

static void
getTiles(PanexWidget 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->panex.cheat = True; /* Assume the worst. */
}

static void
writeTiles(PanexWidget 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, "tiles%c %d\n", SYMBOL, w->panex.tiles);
	(void) fprintf(fp, "mode%c %d\n", SYMBOL, w->panex.mode);
	(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(PanexWidget w)
{
	if (madeMoves(&undo) &&
			w->panex.currentStack < 0) {
		int fromStack, fromPosition, toStack;

		getMove(&undo, &toStack, &fromStack);
		setMove(&redo, toStack, fromStack);
		if ((fromPosition = topOfStack(w, fromStack, 0)) < 0 ||
				moveTile(w, fromStack, fromPosition, toStack,
					DOUBLE) < 0) {
			char *buf1, *buf2;

			intCat(&buf1, "Move from ", fromStack);
			stringCat(&buf2, buf1, " to ");
			free(buf1);
			intCat(&buf1, buf2, toStack);
			free(buf2);
			stringCat(&buf2, buf1, " cannot be made");
			free(buf1);
			DISPLAY_WARNING(buf2);
			free(buf2);
		} else {
			setPuzzle(w, ACTION_UNDO);
		}
	}
}

static void
redoTiles(PanexWidget w)
{
	if (madeMoves(&redo) &&
			w->panex.currentStack < 0) {
		int fromStack, fromPosition, toStack;

		getMove(&redo, &fromStack, &toStack);
		setMove(&undo, fromStack, toStack);
		if ((fromPosition = topOfStack(w, fromStack, 0)) < 0 ||
				moveTile(w, fromStack, fromPosition, toStack,
					DOUBLE) < 0) {
			char *buf1, *buf2;

			intCat(&buf1, "Move from ", fromStack);
			stringCat(&buf2, buf1, " to ");
			free(buf1);
			intCat(&buf1, buf2, toStack);
			free(buf2);
			stringCat(&buf2, buf1, " cannot be made");
			free(buf1);
			DISPLAY_WARNING(buf2);
			free(buf2);
		} else {
			setPuzzle(w, ACTION_REDO);
		}
	}
}

static void
clearTiles(PanexWidget w)
{
	if (w->panex.currentStack >= 0)
		return;
	eraseFrame(w, 0, w->panex.focus);
	resetTiles(w);
	drawSlots(w, 0);
	drawAllTiles(w);
	drawFrame(w, 0, w->panex.focus);
	setPuzzle(w, ACTION_RESET);
}

static void
solveTiles(PanexWidget w)
{
	/* Cheat and Reset To Start */
	eraseFrame(w, 0, w->panex.focus);
	resetTiles(w);
	drawSlots(w, 0);
	drawAllTiles(w);
	drawFrame(w, 0, w->panex.focus);
	setPuzzle(w, ACTION_RESET);
	solveTilesFromStart(w);
}

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

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

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

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

static void
decrementTiles(PanexWidget w)
{
	if (w->panex.tiles <= MIN_TILES)
		return;
	setPuzzle(w, ACTION_DECREMENT);
}

static void
modeTiles(PanexWidget w)
{
	setPuzzle(w, ACTION_MODE);
}

int
movePuzzle(PanexWidget w, int fromStack, int fromPosition, int toStack,
		int fast)
{
	int toPosition;

	if ((toPosition = moveTile(w, fromStack, fromPosition, toStack,
			fast)) >= 0) {
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, fromStack, toStack);
		flushMoves(w, &redo, FALSE);
	}
	return toPosition;
}

#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(PanexWidget w)
{
	struct tagColor {
		int red, green, blue;
	} color;
	char szBuf[80], buf[20], charbuf[2];
	int pyramid;

	w->panex.tiles = (int) GetPrivateProfileInt(SECTION,
		"tiles", DEFAULT_TILES, INIFILE);
	w->panex.mode = (int) GetPrivateProfileInt(SECTION,
		"mode", DEFAULT_MODE, INIFILE);
	w->panex.stacks = ((w->panex.mode == HANOI) ? 1 : 2);
	w->panex.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->panex.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->panex.tileGC[1] = RGB(color.red, color.green, color.blue);
	w->panex.tileGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->panex.tileGC[2] = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	/* 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->panex.frameGC = 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->panex.inverseGC[1] = RGB(color.red, color.green, color.blue);
	w->panex.inverseGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->panex.inverseGC[2] = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	w->panex.inverseGC[3] = RGB(darker(darker(color.red)),
		darker(darker(color.green)), darker(darker(color.blue)));
	w->panex.inverseGC[4] = RGB(darker(darker(darker(color.red))),
		darker(darker(darker(color.green))),
		darker(darker(darker(color.blue))));
	for (pyramid = 0; pyramid < w->panex.stacks; pyramid++) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "pyramidColor%d", pyramid);
#else
		(void) sprintf(buf, "pyramidColor%d", pyramid);
#endif
		(void) GetPrivateProfileString(SECTION,
			buf, pyramidColorString[pyramid],
			szBuf, sizeof (szBuf), INIFILE);
		(void) sscanf(szBuf, "%d %d %d",
			&(color.red), &(color.green), &(color.blue));
		w->panex.pyramidGC[pyramid] =
			RGB(color.red, color.green, color.blue);
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "pyramidChar%d", pyramid);
#else
		(void) sprintf(buf, "pyramidChar%d", pyramid);
#endif
		charbuf[0] = pyramidColorChar[pyramid];
		charbuf[1] = '\0';
		(void) GetPrivateProfileString(SECTION,
			buf, charbuf, szBuf, sizeof (szBuf), INIFILE);
		w->panex.pyramidChar[pyramid] = szBuf[0];
	}
	w->panex.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", DEFAULT_DELAY, INIFILE);
	w->panex.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", 0, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"moveSound", MOVESOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->panex.moveSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"dripSound", DRIPSOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->panex.dripSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->panex.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->panex.scoreFile, szBuf, 80);
}

void
destroyPuzzle(HBRUSH brush)
{
	deleteMoves(&undo);
	deleteMoves(&redo);
	(void) DeleteObject(brush);
	PostQuitMessage(0);
}

#else
#define MAX_INTENSITY 0xFFFF

static Pixel
brighter(PanexWidget 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(PanexWidget 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
getColor(PanexWidget w, int pyramid)
{
	XGCValues values;
	XtGCMask valueMask;
	XColor colorCell, rgb;

	valueMask = GCForeground | GCBackground;
	if (w->panex.reverse) {
		values.background = w->panex.foreground;
	} else {
		values.background = w->panex.background;
	}
	if (!w->panex.mono) {
		if (XAllocNamedColor(XtDisplay(w),
				DefaultColormapOfScreen(XtScreen(w)),
				w->panex.pyramidName[pyramid], &colorCell, &rgb)) {
			values.foreground = w->panex.pyramidColor[pyramid] =
				colorCell.pixel;
			if (w->panex.pyramidGC[pyramid])
				XtReleaseGC((Widget) w,
					w->panex.pyramidGC[pyramid]);
			w->panex.pyramidGC[pyramid] = XtGetGC((Widget) w,
				valueMask, &values);
			return;
		} else {
			char *buf1, *buf2;

			stringCat(&buf1, "Color name \"",
				w->panex.pyramidName[pyramid]);
			stringCat(&buf2, buf1,
				"\" is not defined for pyramid ");
			free(buf1);
			intCat(&buf1, buf2, pyramid);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
		}
	}
	if (w->panex.reverse) {
		values.background = w->panex.foreground;
		values.foreground = w->panex.background;
	} else {
		values.background = w->panex.background;
		values.foreground = w->panex.foreground;
	}
	if (w->panex.pyramidGC[pyramid])
		XtReleaseGC((Widget) w, w->panex.pyramidGC[pyramid]);
	w->panex.pyramidGC[pyramid] = XtGetGC((Widget) w, valueMask, &values);
}

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

	valueMask = GCForeground | GCBackground;
	if (w->panex.reverse) {
		values.foreground = w->panex.foreground;
		values.background = w->panex.background;
	} else {
		values.background = w->panex.foreground;
		values.foreground = w->panex.background;
	}
	if (w->panex.inverseGC[1])
		XtReleaseGC((Widget) w, w->panex.inverseGC[1]);
	w->panex.inverseGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->panex.mono) {
		values.foreground = brighter(w, (w->panex.reverse) ?
			w->panex.foreground : w->panex.background);
	}
	if (w->panex.inverseGC[0])
		XtReleaseGC((Widget) w, w->panex.inverseGC[0]);
	w->panex.inverseGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->panex.mono) {
		values.foreground = darker(w, (w->panex.reverse) ?
			w->panex.foreground : w->panex.background);
	}
	if (w->panex.inverseGC[2])
		XtReleaseGC((Widget) w, w->panex.inverseGC[2]);
	w->panex.inverseGC[2] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->panex.mono) {
		values.foreground = darker(w, darker(w, (w->panex.reverse) ?
			w->panex.foreground : w->panex.background));
	}
	if (w->panex.inverseGC[3])
		XtReleaseGC((Widget) w, w->panex.inverseGC[3]);
	w->panex.inverseGC[3] = XtGetGC((Widget) w, valueMask, &values);
	if (w->panex.mono) {
		if (w->panex.reverse) {
			values.background = w->panex.foreground;
			values.foreground = w->panex.background;
		} else {
			values.foreground = w->panex.foreground;
			values.background = w->panex.background;
		}
	} else {
		values.foreground = darker(w, darker(w, darker(w,
			(w->panex.reverse) ?
			w->panex.background : w->panex.foreground)));
	}
	if (w->panex.inverseGC[4])
		XtReleaseGC((Widget) w, w->panex.inverseGC[4]);
	w->panex.inverseGC[4] = XtGetGC((Widget) w, valueMask, &values);
	if (w->panex.reverse) {
		if (w->panex.mono) {
			values.background = w->panex.foreground;
			values.foreground = w->panex.background;
		} else {
			values.background = w->panex.tileColor;
			values.foreground = w->panex.borderColor;
		}
	} else {
		if (w->panex.mono) {
			values.foreground = w->panex.foreground;
			values.background = w->panex.background;
		} else {
			values.foreground = w->panex.tileColor;
			values.background = w->panex.borderColor;
		}
	}
	if (w->panex.tileGC[1])
		XtReleaseGC((Widget) w, w->panex.tileGC[1]);
	w->panex.tileGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->panex.mono) {
		values.foreground = brighter(w, w->panex.tileColor);
	}
	if (w->panex.tileGC[0])
		XtReleaseGC((Widget) w, w->panex.tileGC[0]);
	w->panex.tileGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->panex.mono) {
		values.foreground = darker(w, w->panex.tileColor);
	}
	if (w->panex.tileGC[2])
		XtReleaseGC((Widget) w, w->panex.tileGC[2]);
	w->panex.tileGC[2] = XtGetGC((Widget) w, valueMask, &values);
	for (pyramid = 0; pyramid < w->panex.stacks; pyramid++)
		getColor(w, pyramid);
	if (w->panex.fontInfo)
		XSetFont(XtDisplay(w), w->panex.inverseGC[4],
			w->panex.fontInfo->fid);
	if (w->panex.mono) {
		if (w->panex.reverse) {
			values.foreground = w->panex.background;
			values.background = w->panex.foreground;
		} else {
			values.foreground = w->panex.foreground;
			values.background = w->panex.background;
		}
	} else {
		values.foreground = w->panex.frameColor;
		values.background = w->panex.borderColor;
	}
	if (w->panex.frameGC)
		XtReleaseGC((Widget) w, w->panex.frameGC);
	w->panex.frameGC = XtGetGC((Widget) w, valueMask, &values);
	w->panex.grainLR = XCreateBitmapFromData(display, window,
		(char *) grain_lr_bits, grain_lr_width, grain_lr_height);
	w->panex.grainTB = XCreateBitmapFromData(display, window,
		(char *) grain_tb_bits, grain_tb_width, grain_tb_height);

}

static Boolean
setValuesPuzzle(Widget current, Widget request, Widget renew)
{
	PanexWidget c = (PanexWidget) current, w = (PanexWidget) renew;
	Boolean redraw = False;
	Boolean redrawTiles = False, setColors = False;
	int pyramid;

	checkTiles(w);
	if (w->panex.mode == c->panex.mode) {
		for (pyramid = 0; pyramid < w->panex.stacks; pyramid++) {
			if (strcmp(w->panex.pyramidName[pyramid],
					c->panex.pyramidName[pyramid])) {
				setColors = True;
				break;
			}
		}
	}
	if (w->panex.font != c->panex.font ||
			w->panex.reverse != c->panex.reverse ||
			w->panex.mono != c->panex.mono ||
			setColors) {
		loadFont(w);
		setAllColors(w);
		redrawTiles = True;
	} else if (w->panex.background != c->panex.background ||
			w->panex.foreground != c->panex.foreground ||
			w->panex.tileColor != c->panex.tileColor ||
			w->panex.reverse != c->panex.reverse ||
			w->panex.mono != c->panex.mono ||
			setColors) {
		setAllColors(w);
		redrawTiles = True;
	}
	if (w->panex.mode != c->panex.mode) {
		eraseFrame(c, 0, False);
		sizePuzzle(w);
		redraw = True;
	} else if (w->panex.tiles != c->panex.tiles) {
		eraseFrame(c, 0, False);
		sizePuzzle(w);
		redraw = True;
	} else if (w->panex.puzzleOffset.x != c->panex.puzzleOffset.x ||
			w->panex.puzzleOffset.y != c->panex.puzzleOffset.y) {
		eraseFrame(c, 0, False);
		resizePuzzle(w);
		redraw = True;
	}
	if (w->panex.delay != c->panex.delay) {
		w->panex.numSlices = ((w->panex.delay < MAX_SLICES) ?
			w->panex.delay + 1 : MAX_SLICES);
	}
	if (w->panex.menu != ACTION_IGNORE) {
		int menu = w->panex.menu;

		w->panex.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_SOLVE:
			solveTiles(w);
			break;
		case ACTION_MODE:
			modeTiles(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(w, 0, w->panex.focus);
		drawSlots(w, 0);
		drawAllTiles(w);
	}
	return (redraw);
}

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

#if defined( USE_SOUND ) && defined( USE_ESOUND )
	(void) shutdown_sound();
#endif
	for (i = 0; i < w->panex.stacks; i++)
		XtReleaseGC(old, w->panex.pyramidGC[i]);
	for (i = 0; i < FG_SHADES; i++)
		XtReleaseGC(old, w->panex.tileGC[i]);
	for (i = 0; i < BG_SHADES; i++)
		XtReleaseGC(old, w->panex.inverseGC[i]);
	XtRemoveCallbacks(old, XtNselectCallback, w->panex.select);
	if (w->panex.fontInfo) {
		XUnloadFont(display, w->panex.fontInfo->fid);
		XFreeFont(display, w->panex.fontInfo);
	}
	deleteMoves(&undo);
	deleteMoves(&redo);
}

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

static void
resizeTiles(PanexWidget w)
{
	int sel;
#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 (sel = 0; sel < 2; sel++) {
		if (w->panex.bufferTiles[sel] != NULL) {
			DeleteObject(w->panex.bufferTiles[sel]);
			w->panex.bufferTiles[sel] = NULL;
		}
		if ((w->panex.bufferTiles[sel] =
				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->panex.colormap == None) {
		w->panex.mono = (xgwa.depth < 2 || w->panex.mono);
		w->panex.colormap = xgwa.colormap;
	}
	for (sel = 0; sel < 2; sel++) {
		if (w->panex.bufferTiles[sel] != None) {
			XFreePixmap(display, w->panex.bufferTiles[sel]);
			w->panex.bufferTiles[sel] = None;
		}
		if ((w->panex.bufferTiles[sel] = XCreatePixmap(display,
				window, w->core.width, w->core.height,
				xgwa.depth)) == None) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	setAllColors(w);
#endif
	drawAllBufferedTiles(w);
}

#ifndef WINVER
static
#endif
void
resizePuzzle(PanexWidget w)
{
#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->panex.delta.x = 8;
	w->panex.delta.y = 2;
	w->panex.frameThickness = MIN(16, MIN(w->core.width, w->core.height) / 16);
	w->panex.pos.x = MAX(((int) w->core.width - w->panex.delta.x - 2 * w->panex.frameThickness) /
			MAX_STACKS, w->panex.delta.x);
	w->panex.pos.y = MAX(((int) w->core.height - 2 * w->panex.delta.y - 3 - 2 * w->panex.frameThickness) /
			(w->panex.tiles + w->panex.blanks), w->panex.delta.y);
	w->panex.width = w->panex.pos.x * MAX_STACKS + w->panex.delta.x + 2;
	w->panex.height = w->panex.pos.y * (w->panex.tiles + w->panex.blanks) +
		w->panex.delta.y + 3;
	w->panex.puzzleOffset.x = ((int) w->core.width - w->panex.width + 2) / 2;
	w->panex.puzzleOffset.y = ((int) w->core.height - w->panex.height + 2) / 2;
	/* Make the following odd */
	w->panex.tileSize.x = (((w->panex.pos.x - w->panex.delta.x) >> 1) << 1) + 1;
	w->panex.tileSize.y = w->panex.pos.y - w->panex.delta.y + 1;
	w->panex.letterOffset.x = 3;
	w->panex.letterOffset.y = 4;
}

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

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

	(void) SRAND(getpid());
	w->panex.stacks = ((w->panex.mode == HANOI) ? 1 : 2);
	for (stack = 0; stack < w->panex.stacks; stack++)
		w->panex.pyramidGC[stack] = NULL;
	w->panex.bufferTiles[0] = None;
	w->panex.bufferTiles[1] = None;
	w->panex.colormap = None;
	w->panex.fontInfo = NULL;
	for (i = 0; i < FG_SHADES; i++)
		w->panex.tileGC[i] = NULL;
	for (i = 0; i < BG_SHADES; i++)
		w->panex.inverseGC[i] = NULL;
#endif
	w->panex.focus = False;
	loadFont(w);
	for (stack = 0; stack < MAX_STACKS; stack++) {
		w->panex.tileOfPosition[stack] = NULL;
	}
	for (stack = 0; stack < w->panex.stacks; stack++) {
		w->panex.positionOfTile[stack] = NULL;
	}
	checkTiles(w);
	newMoves(&undo);
	newMoves(&redo);;
	w->panex.numSlices = ((w->panex.delay < MAX_SLICES) ?
		w->panex.delay + 1 : MAX_SLICES);
	sizePuzzle(w);
#ifdef USE_SOUND
#ifdef USE_NAS
	dsp = XtDisplay(w);
#endif
#ifdef USE_ESOUND
	(void) init_sound();
#endif
#endif
}

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

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

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

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

	w->panex.previousStack = -1;
	if (checkSolved(w)) {
		moveNoTiles(w);
	} else if (((stack = selectTile(w, x)) >= 0) &&
			((w->panex.currentPosition = topOfStack(w, stack, 0)) >= 0)) {
		w->panex.currentStack = stack;
		if (moveTileToLimbo(w, w->panex.currentStack,
				w->panex.currentPosition, w->panex.currentStack, False)) {
			w->panex.currentPosition = 0;
			w->panex.previousStack = w->panex.currentStack;
		} else if (moveTileToLimbo(w, w->panex.currentStack,
				w->panex.currentPosition, w->panex.currentStack, True)) {
			w->panex.currentPosition = 0;
			w->panex.previousStack = w->panex.currentStack;
		}
		drawTile(w, stack, w->panex.currentPosition, False,
			TRUE, 0, 0);
	} else
		w->panex.currentStack = -1;
}

#ifndef WINVER
static
#endif
void
motionPuzzle(PanexWidget w
#ifdef WINVER
, const int x
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	int toStack;
#ifndef WINVER
	int x = event->xbutton.x;
#endif

	if (w->panex.currentStack < 0)
		return;
	if ((toStack = selectTile(w, x)) >= 0 &&
			toStack != w->panex.currentStack) {
		if (moveTileToLimbo(w, w->panex.currentStack,
				w->panex.currentPosition, toStack, True)) {
#if 0
	drawTile(w, w->panex.currentStack, w->panex.currentPosition,
		True, TRUE, 0, 0);
#endif
			if (w->panex.previousStack < 0) {
				w->panex.previousStack = w->panex.currentStack;
			}
			w->panex.currentStack = toStack;
			w->panex.currentPosition = 0;
			drawTile(w, toStack, w->panex.currentPosition, False,
				TRUE, 0, 0);
		} else if (moveTileToLimbo(w, w->panex.currentStack,
				w->panex.currentPosition, toStack, False)) {
#if 0
	drawTile(w, w->panex.currentStack, w->panex.currentPosition,
		True, TRUE, 0, 0);
#endif
			w->panex.currentStack = toStack;
			w->panex.currentPosition = 0;
			drawTile(w, toStack, w->panex.currentPosition, False,
				TRUE, 0, 0);
		}
	}
}

#ifndef WINVER
static
#endif
void
releasePuzzle(PanexWidget w
#ifdef WINVER
, const int x
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	int toStack, toPosition;
#ifndef WINVER
	int x = event->xbutton.x;
#endif

	if (w->panex.currentStack < 0)
		return;
	drawTile(w, w->panex.currentStack, w->panex.currentPosition,
		True, TRUE, 0, 0);
	drawTile(w, w->panex.currentStack, w->panex.currentPosition,
		False, FALSE, 0, 0);
	if ((toStack = selectTile(w, x)) >= 0 &&
			((w->panex.previousStack < 0 &&
			toStack != w->panex.currentStack) ||
			(w->panex.previousStack >= 0))) {
		if ((toPosition = moveTile(w, w->panex.currentStack,
				w->panex.currentPosition, toStack,
				NORMAL)) >= 0) {
			if (w->panex.previousStack != toStack) {
				setPuzzle(w, ACTION_MOVED);
				if (w->panex.previousStack >= 0) {
					setMove(&undo, w->panex.previousStack,
						toStack);
					flushMoves(w, &redo, FALSE);
				} else {
					setMove(&undo, w->panex.currentStack,
						toStack);
					flushMoves(w, &redo, FALSE);
				}
				if (checkSolved(w)) {
					setPuzzle(w, ACTION_SOLVED);
				}
			}
		} else if (toPosition == -1) {
			setPuzzle(w, ACTION_BLOCKED);
			if ((w->panex.previousStack != -1) &&
					((toPosition = moveTile(w,
					w->panex.currentStack,
					w->panex.currentPosition,
					w->panex.currentStack,
					NORMAL)) >= 0) &&
					(w->panex.previousStack != toStack) &&
					(w->panex.previousStack !=
						w->panex.currentStack)) {
				setPuzzle(w, ACTION_MOVED);
				if (w->panex.previousStack >= 0) {
					setMove(&undo,
						w->panex.previousStack,
						toStack);
					flushMoves(w, &redo, FALSE);
				} else {
					setMove(&undo,
						w->panex.currentStack,
						toStack);
					flushMoves(w, &redo, FALSE);
				}
				if (checkSolved(w)) {
					setPuzzle(w, ACTION_SOLVED);
				}
			}
		} else {
			setPuzzle(w, ACTION_ILLEGAL);
			if (w->panex.previousStack != -1) {
				toPosition = moveTile(w, w->panex.currentStack,
				w->panex.currentPosition,
				w->panex.previousStack, NORMAL);
			}
		}
	}
	w->panex.previousStack = -1;
	w->panex.currentStack = -1;
}

#ifndef WINVER
void
clearWithQueryPuzzle(PanexWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->panex.started)
		clearTiles(w);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	else {
		/* Check if one really wants to destroy current state. */
		setPuzzle(w, ACTION_CLEAR_QUERY);
	}
#endif
}

void
clearWithDoubleClickPuzzle(PanexWidget w
, XEvent *event, char **args, int nArgs
)
{
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	clearTiles(w);
#endif
}
#endif

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

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

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

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

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

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

void
modePuzzle(PanexWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	modeTiles(w);
}

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

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

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

void
incrementPuzzle(PanexWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	incrementTiles(w);
}

void
decrementPuzzle(PanexWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	decrementTiles(w);
}

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

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