/*
 * @(#)Cubes.c
 *
 * Copyright 1994 - 2025  David A. Bagley, bagleyd AT verizon.net
 *
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of the author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * This program is distributed in the hope that it will be "useful",
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/* Methods file for Cubes */

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

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

#ifdef WINVER
#define hasValue(x) (*(x))
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "C:/ProgramData/wpuzzles/wcubes.ini"
#endif
#define SECTION "setup"
#else
#define hasValue(x) (x && *(x))
#include "picture.h"
#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(CubesWidget w, XEvent *event, char **args, int nArgs);
static void destroyPuzzle(Widget old);
static void resizePuzzle(CubesWidget w);
static void sizePuzzle(CubesWidget w);
static void initializePuzzle(Widget request, Widget renew);
static void exposePuzzle(Widget renew, XEvent *event, Region region);
static void hidePuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void selectPuzzle(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void releasePuzzle(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void clearWithQueryPuzzle(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void clearWithDoubleClickPuzzle(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void getPuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void writePuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void undoPuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void redoPuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void clearPuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void randomizePuzzle(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void solvePuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void speedUpPuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void slowDownPuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void toggleSoundPuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void enterPuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void leavePuzzle(CubesWidget w, XEvent *event, char **args, int nArgs);
static void movePuzzleOut(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTop(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleIn(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(CubesWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(CubesWidget 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>o: MoveOut()\n\
 <KeyPress>F11: MoveOut()\n\
 <KeyPress>KP_Divide: MoveOut()\n\
 <KeyPress>R5: MoveOut()\n\
 <KeyPress>Up: MoveTop()\n\
 <KeyPress>osfUp: MoveTop()\n\
 <KeyPress>KP_Up: MoveTop()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\n\
 <KeyPress>Left: MoveLeft()\n\
 <KeyPress>osfLeft: MoveLeft()\n\
 <KeyPress>KP_Left: MoveLeft()\n\
 <KeyPress>KP_4: MoveLeft()\n\
 <KeyPress>R10: MoveLeft()\n\
 <KeyPress>i: MoveIn()\n\
 <KeyPress>F12: MoveIn()\n\
 <KeyPress>Begin: MoveIn()\n\
 <KeyPress>KP_5: MoveIn()\n\
 <KeyPress>R11: MoveIn()\n\
 <KeyPress>Right: MoveRight()\n\
 <KeyPress>osfRight: MoveRight()\n\
 <KeyPress>KP_Right: MoveRight()\n\
 <KeyPress>KP_6: MoveRight()\n\
 <KeyPress>R12: MoveRight()\n\
 <KeyPress>Down: MoveBottom()\n\
 <KeyPress>osfDown: MoveBottom()\n\
 <KeyPress>KP_Down: MoveBottom()\n\
 <KeyPress>KP_2: MoveBottom()\n\
 <KeyPress>R14: MoveBottom()\n\
 <Btn1Down>: Select()\n\
 <Btn1Up>: Release()\n\
 <Btn3Down>: ClearMaybe()\n\
 <Btn3Down>(2+): Clear2()\n\
 <Btn4Down>: MoveTop()\n\
 <Btn5Down>: MoveBottom()\n\
 <KeyPress>g: Get()\n\
 <KeyPress>w: Write()\n\
 <KeyPress>u: Undo()\n\
 <KeyPress>r: Redo()\n\
 <KeyPress>c: Clear()\n\
 <KeyPress>z: Randomize()\n\
 <KeyPress>s: Solve()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsList[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "MoveOut", (XtActionProc) movePuzzleOut},
	{(char *) "MoveTop", (XtActionProc) movePuzzleTop},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveIn", (XtActionProc) movePuzzleIn},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleBottom},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "ClearMaybe", (XtActionProc) clearWithQueryPuzzle},
	{(char *) "Clear2", (XtActionProc) clearWithDoubleClickPuzzle},
	{(char *) "Get", (XtActionProc) getPuzzle},
	{(char *) "Write", (XtActionProc) writePuzzle},
	{(char *) "Undo", (XtActionProc) undoPuzzle},
	{(char *) "Redo", (XtActionProc) redoPuzzle},
	{(char *) "Clear", (XtActionProc) clearPuzzle},
	{(char *) "Randomize", (XtActionProc) randomizePuzzle},
	{(char *) "Solve", (XtActionProc) solvePuzzle},
	{(char *) "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(CubesWidget, core.width),
	 XtRString, (caddr_t) "516"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(CubesWidget, core.height),
	 XtRString, (caddr_t) "516"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(CubesWidget, cubes.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(CubesWidget, cubes.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(CubesWidget, cubes.frameColor),
	 XtRString, (caddr_t) "wheat4" /*XtDefaultForeground*/},
	{XtNblockColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(CubesWidget, cubes.blockColor),
	 XtRString, (caddr_t) "gray75" /*XtDefaultForeground*/},
	{XtNtextColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(CubesWidget, cubes.textColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultBackground*/},
	{XtNstippleFrame, XtCStippleFrame, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.stippleFrame),
	 XtRString, (caddr_t) "TRUE"},
	{XtNinstall, XtCInstall, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.install),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpicture, XtCPicture, XtRString, sizeof (String),
	 XtOffset(CubesWidget, cubes.picture),
	 XtRString, (caddr_t) PICTURE},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(CubesWidget, cubes.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNbumpSound, XtCBumpSound, XtRString, sizeof (String),
	 XtOffset(CubesWidget, cubes.bumpSound),
	 XtRString, (caddr_t) BUMPSOUND},
	{XtNdripSound, XtCDripSound, XtRString, sizeof (String),
	 XtOffset(CubesWidget, cubes.dripSound),
	 XtRString, (caddr_t) DRIPSOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(CubesWidget, cubes.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNsizeX, XtCSizeX, XtRInt, sizeof (int),
	 XtOffset(CubesWidget, cubes.sizeX),
	 XtRString, (caddr_t) "4"}, /* DEFAULT_BLOCKSX */
	{XtNsizeY, XtCSizeY, XtRInt, sizeof (int),
	 XtOffset(CubesWidget, cubes.sizeY),
	 XtRString, (caddr_t) "4"}, /* DEFAULT_BLOCKSY */
	{XtNsizeZ, XtCSizeZ, XtRInt, sizeof (int),
	 XtOffset(CubesWidget, cubes.sizeZ),
	 XtRString, (caddr_t) "1"}, /* DEFAULT_BLOCKSZ */
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(CubesWidget, cubes.base),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_BASE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(CubesWidget, cubes.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(CubesWidget, cubes.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(CubesWidget, cubes.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(CubesWidget, cubes.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(CubesWidget, cubes.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(CubesWidget, cubes.select),
	 XtRCallback, (caddr_t) NULL}
};

CubesClassRec cubesClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Cubes",	/* class name */
		sizeof (CubesRec),	/* 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 cubesWidgetClass = (WidgetClass) & cubesClassRec;

void
setPuzzle(CubesWidget w, int reason)
{
	cubesCallbackStruct cb;

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

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

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

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

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

static void
checkBlocks(CubesWidget w)
{
	char *buf1 = NULL, *buf2 = NULL;

	if (w->cubes.sizeX < MIN_BLOCKS) {
		intCat(&buf1,
			"Number of cubes in X direction out of bounds, use at least ",
			MIN_BLOCKS);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_BLOCKSX);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->cubes.sizeX = DEFAULT_BLOCKSX;
	}
	if (w->cubes.sizeY < MIN_BLOCKS) {
		intCat(&buf1,
			"Number of cubes in Y direction out of bounds, use at least ",
			MIN_BLOCKS);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_BLOCKSY);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->cubes.sizeY = DEFAULT_BLOCKSY;
	}
	if (w->cubes.sizeZ < MIN_BLOCKS) {
		intCat(&buf1,
			"Number of cubes in Z direction out of bounds, use at least ",
			MIN_BLOCKS);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_BLOCKSZ);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->cubes.sizeZ = DEFAULT_BLOCKSZ;
	}
	if (w->cubes.delay < 0) {
		intCat(&buf1, "Delay cannot be negative (",
			w->cubes.delay);
		stringCat(&buf2, buf1, "), taking absolute value");
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		w->cubes.delay = -w->cubes.delay;
	}
	if (w->cubes.base < MIN_BASE || w->cubes.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->cubes.base = DEFAULT_BASE;
	}
}

static int
toPosition(CubesWidget w, int column, int row, int stack)
{
	return (column + w->cubes.sizeX * row + w->cubes.sizeRect * stack);
}

static int
typeOfMix(CubesWidget w)
{
	if (w->cubes.sizeX >= w->cubes.sizeZ && w->cubes.sizeY >= w->cubes.sizeZ) {
		return 1;
	} else if (w->cubes.sizeX >= w->cubes.sizeY && w->cubes.sizeZ >= w->cubes.sizeY) {
		return 2;
	} else if (w->cubes.sizeY >= w->cubes.sizeX && w->cubes.sizeZ >= w->cubes.sizeX) {
		return 3;
	}
	return -1;
}

static int
toInvPosition(CubesWidget w, int column, int row, int stack) {
	int mix = typeOfMix(w);

	if (mix == 1) { /* swap x/y */
		return (row + w->cubes.sizeY * column + w->cubes.sizeRect * stack);
	} else if (mix == 2 || mix == 3) { /* swap x/z */
		return (stack + w->cubes.sizeZ * column + w->cubes.sizeX * w->cubes.sizeZ * row);
	}
	return -1;
}

static Boolean
decideSwap(CubesWidget w)
{
	int mix = typeOfMix(w);

	if (mix == 1) { /* swap x/y */
		return (((w->cubes.sizeX >> 1) & 1) == 1
			&& ((w->cubes.sizeY >> 1) & 1) == 1
			&& (w->cubes.sizeZ & 1) == 1);
	} else if (mix == 2) { /* swap x/z */
		return (((w->cubes.sizeX >> 1) & 1) == 1
			&& ((w->cubes.sizeZ >> 1) & 1) == 1
			&& (w->cubes.sizeY & 1) == 1);
	} else if (mix == 3) { /* swap y/z */
		return (((w->cubes.sizeY >> 1) & 1) == 1
			&& ((w->cubes.sizeZ >> 1) & 1) == 1
			&& (w->cubes.sizeX & 1) == 1);
	}
	return False;
}

/* void
reformatPuzzle(CubesWidget w, int vector) {
	if (vector <= 0) {
		if (w->cubes.sizeX < w->cubes.sizeY)
			w->cubes.sizeY = w->cubes.sizeX;
		if (w->cubes.sizeY < w->cubes.sizeZ)
			w->cubes.sizeZ = w->cubes.sizeY;
	} else if (vector > 0) {
		if (w->cubes.sizeZ > w->cubes.sizeY)
			w->cubes.sizeY = w->cubes.sizeZ;
		if (w->cubes.sizeY > w->cubes.sizeX)
			w->cubes.sizeX = w->cubes.sizeY;
	}
}*/

static int
toColumn(CubesWidget w, int pos)
{
	return ((pos % w->cubes.sizeRect) % w->cubes.sizeX);
}

static int
toRow(CubesWidget w, int pos)
{
	return ((pos % w->cubes.sizeRect) / w->cubes.sizeX);
}

static int
toStack(CubesWidget w, int pos)
{
	return (pos / w->cubes.sizeRect);
}

static int
cartesianX(CubesWidget w, int pos)
{
	int x;

	x = toColumn(w, pos) * w->cubes.offset.x + w->cubes.delta.x +
		w->cubes.puzzleOffset.x + 1;
	if (!w->cubes.vertical) {
		x += toStack(w, pos) * (w->cubes.sizeX * w->cubes.offset.x + 5);
	}
	return x;
}

static int
cartesianY(CubesWidget w, int pos)
{
	int y;

	y = toRow(w, pos) * w->cubes.offset.y + w->cubes.delta.y +
		w->cubes.puzzleOffset.y + 1;
	if (w->cubes.vertical) {
		y += toStack(w, pos) * (w->cubes.sizeY * w->cubes.offset.y + 5);
	}
	return y;
}

static int
blockNFromSpace(CubesWidget w, int n, int direction)
{
	int pos = w->cubes.spacePosition;

	if (direction == LEFT || direction == RIGHT)
		return (pos + ((direction == RIGHT) ? -n : n));
	else if (direction == TOP || direction == BOTTOM)
		return (pos + (w->cubes.sizeX * ((direction == BOTTOM) ?
			-n : n)));
	else /* direction == INWARDS || direction == OUTWARDS */
		return (pos + (w->cubes.sizeRect * ((direction == OUTWARDS) ?
			-n : n)));
}

static int
solvedPosition(CubesWidget w, int block) {
	return (block + 1) % w->cubes.sizeBlock;
}

Boolean
checkSolved(const CubesWidget w)
{
	int i;

	for (i = 1; i < w->cubes.sizeBlock; i++)
		if (w->cubes.blockOfPosition[i - 1] != i)
			return FALSE;
	return TRUE;
}

static int
int2String(CubesWidget 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
fill3DRect(CubesWidget 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(CubesWidget w, Pixmap dr, GC gc, int startX, int startY,
		int sizeX, int sizeY)
{
	FILLRECTANGLE(w, dr, gc,
		startX, startY, sizeX + 1, 1);
	FILLRECTANGLE(w, dr, gc,
		startX, startY, 1, sizeY + 1);
}

static void
drawBlock(CubesWidget w, int pos, Boolean blank, Boolean erase,
		int pressedOffset, int offsetX, int offsetY)
{
	int dx, dy, sx, sy;
	int block = (w->cubes.blockOfPosition[pos] + w->cubes.sizeBlock - 1) %
		w->cubes.sizeBlock;
	Pixmap dr = 0;

	dx = cartesianX(w, pos) + offsetX + pressedOffset;
	dy = cartesianY(w, pos) + offsetY + pressedOffset;
	sx = cartesianX(w, block);
	sy = cartesianY(w, block);
	if (blank) {
		FILLRECTANGLE(w, dr, (erase) ?
			w->cubes.inverseGC[2] : w->cubes.blockGC[1],
			dx, dy,
			w->cubes.blockSize.x, w->cubes.blockSize.y);
		return;
	}
#ifdef WINVER
	w->core.hOldBitmap = (HBITMAP) SelectObject(w->core.memDC,
		w->cubes.bufferBlocks[pressedOffset]);
	BitBlt(w->core.hDC,
		dx, dy,
		w->cubes.blockSize.x, w->cubes.blockSize.y,
		w->core.memDC,
		sx, sy,
		SRCCOPY);
	(void) SelectObject(w->core.memDC, w->core.hOldBitmap);
#else
	XSetGraphicsExposures(XtDisplay(w), w->cubes.blockGC[1], False);
	XCopyArea(XtDisplay(w),
		w->cubes.bufferBlocks[pressedOffset],
		XtWindow(w),
		w->cubes.blockGC[1],
		sx, sy,
		w->cubes.blockSize.x, w->cubes.blockSize.y,
		dx, dy);
#endif
}

static void
drawBlockPiece(CubesWidget w, int pos, Boolean blank, Boolean erase,
		int pressedOffset, int offsetX, int offsetY,
		int lox, int loy, int lsx, int lsy)
{
	int dx, dy, sx, sy;
	int block = (w->cubes.blockOfPosition[pos] + w->cubes.sizeBlock - 1) %
		w->cubes.sizeBlock;
	Pixmap dr = 0;

	dx = cartesianX(w, pos) + offsetX + pressedOffset;
	dy = cartesianY(w, pos) + offsetY + pressedOffset;
	sx = cartesianX(w, block);
	sy = cartesianY(w, block);
	if (blank) {
		FILLRECTANGLE(w, dr, (erase) ?
			w->cubes.inverseGC[2] : w->cubes.blockGC[1],
			dx + lox, dy + loy,
			lsx, lsy);
		return;
	}
#ifdef WINVER
	w->core.hOldBitmap = (HBITMAP) SelectObject(w->core.memDC,
		w->cubes.bufferBlocks[pressedOffset]);
	BitBlt(w->core.hDC,
		dx + lox, dy + loy,
		lsx, lsy,
		w->core.memDC,
		sx + lox, sy + loy,
		SRCCOPY);
	(void) SelectObject(w->core.memDC, w->core.hOldBitmap);
#else
	XSetGraphicsExposures(XtDisplay(w), w->cubes.blockGC[1], False);
	XCopyArea(XtDisplay(w),
		w->cubes.bufferBlocks[pressedOffset],
		XtWindow(w),
		w->cubes.blockGC[1],
		sx + lox, sy + loy,
		lsx, lsy,
		dx + lox, dy + loy);
#endif
}

static void
drawBufferedBlock(CubesWidget w, int pos, int pressedOffset)
{
	Pixmap *dr;
	GC blockGC, textGC;
	int dx, dy, block = solvedPosition(w, pos);
	int i = 0, digitOffsetX = 0;
	char buf[65];

	dr = &(w->cubes.bufferBlocks[pressedOffset]);
	blockGC = w->cubes.blockGC[1];
	textGC = w->cubes.textGC;
	dx = cartesianX(w, pos) + pressedOffset;
	dy = cartesianY(w, pos) + pressedOffset;
	if (pressedOffset != 0) {
		drawShadow(w, *dr, w->cubes.blockGC[2],
			dx - pressedOffset, dy - pressedOffset,
			w->cubes.blockSize.x - 1, w->cubes.blockSize.y - 1);
	}
	fill3DRect(w, *dr, blockGC,
		w->cubes.blockGC[2], w->cubes.blockGC[0],
		dx, dy, w->cubes.blockSize.x, w->cubes.blockSize.y,
		pressedOffset == 0);
	(void) int2String(w, buf, block, w->cubes.base, True);
	while (block >= 1) {
		block /= w->cubes.base;
		digitOffsetX += w->cubes.digitOffset.x;
		i++;
	}
	DRAWTEXT(w, *dr, textGC,
		dx + w->cubes.blockSize.x / 2 - digitOffsetX,
		dy + w->cubes.blockSize.y / 2 + w->cubes.digitOffset.y,
		buf, i);
}

void
drawAllBlocks(const CubesWidget w)
{
	int k;

	for (k = 0; k < w->cubes.sizeBlock; k++)
		drawBlock(w, k, (w->cubes.blockOfPosition[k] <= 0),
			(w->cubes.blockOfPosition[k] <= 0), FALSE, 0, 0);
}

static void
drawAllBufferedBlocks(const CubesWidget w)
{
	int k, l;

	for (k = 0; k < w->cubes.sizeBlock; k++)
		for (l = 0; l < 2; l++)
			drawBufferedBlock(w, k, l);
}

static void
drawBlockFrame(CubesWidget w, int xR, int yR, int wR, int hR, int incAt)
{
	Pixmap dr = 0;

	if (wR > 2 * incAt && hR > 2 * incAt) {
		/* Perfectionism here...
		 * have the erasing square be 3D also */
		FILLRECTANGLE(w, dr,
			w->cubes.blockGC[0],
			xR + incAt, yR + incAt, 2, hR - 2 * incAt);
		FILLRECTANGLE(w, dr,
			w->cubes.blockGC[0],
			xR + incAt, yR + incAt, wR - 2 * incAt, 2);
		FILLRECTANGLE(w, dr,
			w->cubes.blockGC[2],
			xR + wR - incAt - 2, yR + incAt, 2, hR - 2 * incAt);
		FILLRECTANGLE(w, dr,
			w->cubes.blockGC[2],
			xR + incAt, yR + hR - incAt - 2, wR - 2 * incAt, 2);
	}
}

void
animateSlide(CubesWidget w, int numOfBlocks, int dir, int fast, Boolean logMoves)
{
	int inc, aBlock;
	int fillBeginPos, fillBeginI = 0, gapI = 0, moveI = 0;
	int dx, dy, pos, posNext;
	int ix = 0, iy = 0;
	int numBlocks = numOfBlocks;
	Pixmap dr = 0;

	fillBeginPos = blockNFromSpace(w, numBlocks, dir);
	if ((dir == RIGHT) || (dir == LEFT)) {
		fillBeginI = cartesianX(w, fillBeginPos);
		gapI = w->cubes.blockSize.x * fast / w->cubes.numSlices;
		moveI = w->cubes.blockSize.x + w->cubes.delta.x;
	} else if ((dir == TOP) || (dir == BOTTOM)) {
		fillBeginI = cartesianY(w, fillBeginPos);
		gapI = w->cubes.blockSize.y * fast / w->cubes.numSlices;
		moveI = w->cubes.blockSize.y + w->cubes.delta.y;
	}
	if (gapI == 0)
		gapI++;
	if (numBlocks < 0)
		numBlocks = -numBlocks;
	FLUSH(w);
	initTimer(w->cubes.oldTime);
	for (inc = 0; inc < moveI + gapI; inc += gapI) {
	  if (inc > moveI)
	    inc = moveI;
	  for (aBlock = 0; aBlock < numBlocks; aBlock++) {
	    posNext = blockNFromSpace(w, aBlock + 1, dir);
	    if (logMoves && inc == 0) {
	      setPuzzle(w, ACTION_MOVED);
	      setMove(&undo, dir);
	      flushMoves(w, &redo, FALSE);
	    }
	    /* Calculate deltas */
	    dx = cartesianX(w, posNext);
	    dy = cartesianY(w, posNext);
	    if ((dir == RIGHT) || (dir == LEFT)) {
	      ix = ((dir == RIGHT) ? inc : -inc);
	      iy = 0;
	    } else if ((dir == TOP) || (dir == BOTTOM)) {
	      ix = 0;
	      iy = ((dir == BOTTOM) ? inc : -inc);
	    }
	    drawBlock(w, posNext, False, False, FALSE, ix, iy);
	    /* Erase old slivers */
	    ix += dx;
	    iy += dy;
	    if (aBlock < numBlocks - 1) {
	      switch (dir) {
	      case TOP:
		FILLRECTANGLE(w, dr,
		  w->cubes.inverseGC[2],
		  ix, iy + w->cubes.blockSize.y,
		  w->cubes.blockSize.x, gapI);
		break;
	      case RIGHT:
		FILLRECTANGLE(w, dr,
		  w->cubes.inverseGC[2],
		  ix - gapI,
		  iy, gapI, w->cubes.blockSize.y);
		break;
	      case BOTTOM:
		FILLRECTANGLE(w, dr,
		  w->cubes.inverseGC[2],
		  ix, iy - gapI,
		  w->cubes.blockSize.x, gapI);
		break;
	      case LEFT:
		FILLRECTANGLE(w, dr,
		  w->cubes.inverseGC[2],
		  ix + w->cubes.blockSize.x, iy,
		  gapI, w->cubes.blockSize.y);
		break;
	      }
	    } else {
	      switch (dir) {
	      case TOP:
		FILLRECTANGLE(w, dr,
		  w->cubes.inverseGC[2],
		  ix, iy + w->cubes.blockSize.y,
		  w->cubes.blockSize.x, fillBeginI - iy);
		break;
	      case RIGHT:
		FILLRECTANGLE(w, dr,
		  w->cubes.inverseGC[2],
		  fillBeginI, iy,
		  ix - fillBeginI, w->cubes.blockSize.y);
		break;
	      case BOTTOM:
		FILLRECTANGLE(w, dr,
		  w->cubes.inverseGC[2],
		  ix, fillBeginI,
		  w->cubes.blockSize.x, iy - fillBeginI);
		break;
	      case LEFT:
		FILLRECTANGLE(w, dr,
		  w->cubes.inverseGC[2],
		  ix + w->cubes.blockSize.x, iy,
		  fillBeginI - ix, w->cubes.blockSize.y);
		break;
	      }
	    }
	  }
	  FLUSH(w);
	  useTimer(&(w->cubes.oldTime), w->cubes.delay);
	}
	pos = w->cubes.spacePosition;
	for (aBlock = 0; aBlock < numBlocks; aBlock++) {
	  posNext = blockNFromSpace(w, aBlock + 1, dir);
	  w->cubes.blockOfPosition[pos] =
	    w->cubes.blockOfPosition[posNext];
	  pos = posNext;
	}
	w->cubes.spacePosition = fillBeginPos;
	w->cubes.blockOfPosition[fillBeginPos] = 0;
}

static void
animateMove(CubesWidget w, int fromPos, int fast)
{
	int pos = w->cubes.spacePosition;
	int dx, dy, fx, fy;
	int xToCenter, yToCenter;
	int incTo, incAt, inc;
	Pixmap dr = 0;

	dx = cartesianX(w, pos);
	dy = cartesianY(w, pos);
	fx = cartesianX(w, fromPos);
	fy = cartesianY(w, fromPos);
	xToCenter = w->cubes.blockSize.x / 2;
	yToCenter = w->cubes.blockSize.y / 2;
	incTo = MIN(w->cubes.blockSize.x, w->cubes.blockSize.y) / 2;
	incAt = MAX(incTo * fast / (2 * w->cubes.numSlices), 1);

	FILLRECTANGLE(w, dr,
		w->cubes.inverseGC[2],
		fx, fy, incAt, w->cubes.blockSize.y);
	FILLRECTANGLE(w, dr,
		w->cubes.inverseGC[2],
		fx, fy, w->cubes.blockSize.x, incAt);
	FILLRECTANGLE(w, dr,
		w->cubes.inverseGC[2],
		fx + w->cubes.blockSize.x - incAt, fy,
		incAt, w->cubes.blockSize.y);
	FILLRECTANGLE(w, dr,
		w->cubes.inverseGC[2],
		fx, fy + w->cubes.blockSize.y - incAt,
		w->cubes.blockSize.x, incAt);
	FLUSH(w);
	initTimer(w->cubes.oldTime);
	for (inc = incAt; inc < incTo; inc += incAt) {
		int xR = fx + inc, yR = fy + inc;
		int wR = w->cubes.blockSize.x - 2 * inc;
		int hR = w->cubes.blockSize.y - 2 * inc;

		/* start disappear block */
		if (wR > 0 && hR > 0) {
			FILLRECTANGLE(w, dr,
				w->cubes.inverseGC[2],
				xR, yR, incAt, hR);
			FILLRECTANGLE(w, dr,
				w->cubes.inverseGC[2],
				xR, yR, wR, incAt);
			FILLRECTANGLE(w, dr,
				w->cubes.inverseGC[2],
				xR + wR - incAt, yR, incAt, hR);
			FILLRECTANGLE(w, dr,
				w->cubes.inverseGC[2],
				xR, yR + hR - incAt, wR, incAt);
		}
		if (!hasValue(w->cubes.picture)) {
			drawBlockFrame(w, xR, yR, wR, hR, incAt);
		}
		/* end disappear block */
		/* start reappear blocks */
		if (w->cubes.blockSize.x > w->cubes.blockSize.y) {
			/* horizontal blocks */
			drawBlockPiece(w, pos, False, False, FALSE, 0, 0,
				xToCenter - inc - (w->cubes.blockSize.x / 2 - w->cubes.blockSize.y / 2),
				yToCenter - inc,
				2 * (inc + w->cubes.blockSize.x / 2 - w->cubes.blockSize.y / 2),
				2 * inc);
		if (!hasValue(w->cubes.picture)) {
			drawBlockFrame(w,
				xToCenter - inc - (w->cubes.blockSize.x / 2 - w->cubes.blockSize.y / 2) + dx - incAt,
				yToCenter - inc + dy - incAt,
				2 * (inc + w->cubes.blockSize.x / 2 - w->cubes.blockSize.y / 2) + 2 * incAt,
				2 * inc + 2 * incAt,
				incAt);
		}
#if 0
			fill3DRect(w, dr, w->cubes.blockGC[1],
				w->cubes.blockGC[2],
				w->cubes.blockGC[0],
				xToCenter - inc - (w->cubes.blockSize.x / 2 - w->cubes.blockSize.y / 2) + dx,
				yToCenter - inc + dy,
				2 * (inc + w->cubes.blockSize.x / 2 - w->cubes.blockSize.y / 2),
				2 * inc,
				True);
#endif
		} else {
			/* vertical blocks */
			drawBlockPiece(w, pos, False, False, FALSE, 0, 0,
				xToCenter - inc,
				yToCenter - inc - (w->cubes.blockSize.y / 2 - w->cubes.blockSize.x / 2),
				2 * inc,
				2 * (inc + w->cubes.blockSize.y / 2 - w->cubes.blockSize.x / 2));
		if (!hasValue(w->cubes.picture)) {
			drawBlockFrame(w,
				xToCenter - inc + dx - incAt,
				yToCenter - inc - (w->cubes.blockSize.y / 2 - w->cubes.blockSize.x / 2) + dy - incAt,
				2 * inc + 2 * incAt,
				2 * (inc + w->cubes.blockSize.y / 2 - w->cubes.blockSize.x / 2) + 2 * incAt,
				incAt);
		}
#if 0
			fill3DRect(w, dr, w->cubes.blockGC[1],
				w->cubes.blockGC[2],
				w->cubes.blockGC[0],
				xToCenter - inc + dx,
				yToCenter - inc - (w->cubes.blockSize.y / 2 - w->cubes.blockSize.x / 2) + dy,
				2 * inc,
				2 * (inc + w->cubes.blockSize.y / 2 - w->cubes.blockSize.x / 2),
				True);
#endif
		}
		FLUSH(w);
		useTimer(&(w->cubes.oldTime), w->cubes.delay);
	}
	drawBlock(w, fromPos, True, True, FALSE, 0, 0);
	drawBlock(w, pos, False, False, FALSE, 0, 0);
	FLUSH(w);
	w->cubes.spacePosition = fromPos;
}

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

	if (inside)
		gc = w->cubes.inverseGC[3];
	else
		gc = w->cubes.inverseGC[2];
	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->cubes.inverseGC[0];
	else
		gc = w->cubes.inverseGC[2];
	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
drawFrameLR(const CubesWidget w, Pixmap dr)
{
	GC gc = w->cubes.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->cubes.stippleFrame && w->cubes.grainLR != (Pixmap) NULL) {
		XSetStipple(display, w->cubes.frameGC, w->cubes.grainLR);
		XSetFillStyle(display, w->cubes.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->cubes.frameGC, FillSolid);
#endif
	/* Top */
	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->cubes.puzzleOffset.x - 1;
	tempList[2].y = w->cubes.puzzleOffset.y - 1;
	tempList[3].x = w->cubes.puzzleOffset.x;
	tempList[3].y = w->cubes.puzzleOffset.y - 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
	/* Bottom */
	tempList[0].x = w->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->cubes.puzzleOffset.x;
	tempList[2].y = w->core.height - w->cubes.puzzleOffset.y - 1;
	tempList[3].x = w->core.width - w->cubes.puzzleOffset.x - 1;
	tempList[3].y = w->core.height - w->cubes.puzzleOffset.y - 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
}

static void
drawFrameTB(const CubesWidget w, Pixmap dr)
{
	GC gc = w->cubes.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->cubes.stippleFrame && w->cubes.grainTB != (Pixmap) NULL) {
		XSetStipple(display, w->cubes.frameGC, w->cubes.grainTB);
		XSetFillStyle(display, w->cubes.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->cubes.frameGC, FillSolid);
#endif
	/* Left */
	tempList[0].x = w->cubes.puzzleOffset.x - 1;
	tempList[0].y = w->core.height - w->cubes.puzzleOffset.y - 1;
	tempList[1].x = 0;
	tempList[1].y = w->core.height - 1;
	tempList[2].x = 0;
	tempList[2].y = 0;
	tempList[3].x = w->cubes.puzzleOffset.x - 1;
	tempList[3].y = w->cubes.puzzleOffset.y - 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
	/* Right */
	tempList[0].x = w->core.width - w->cubes.puzzleOffset.x - 1;
	tempList[0].y = w->cubes.puzzleOffset.y;
	tempList[1].x = w->core.width - 1;
	tempList[1].y = 0;
	tempList[2].x = w->core.width - 1;
	tempList[2].y = w->core.height - 1;
	tempList[3].x = w->core.width - w->cubes.puzzleOffset.x - 1;
	tempList[3].y = w->core.height - w->cubes.puzzleOffset.y;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
}

static void
drawFrame(CubesWidget w, Pixmap dr, Boolean focus)
{
	int sumX, sumY, sumZ, offsetX, offsetY, dz, k;

	offsetX = w->cubes.puzzleOffset.x;
	offsetY = w->cubes.puzzleOffset.y;
	drawFrameTB(w, dr);
	drawFrameLR(w, dr);
	/*FILLRECTANGLE(w, dr, w->cubes.inverseGC[4],
		0, 0, w->core.width, offsetY);
	FILLRECTANGLE(w, dr, w->cubes.inverseGC[4],
		0, w->core.height - offsetY,
		w->core.width, offsetY);
	FILLRECTANGLE(w, dr, w->cubes.inverseGC[4],
		0, offsetY, offsetX, w->core.height - offsetY);
	FILLRECTANGLE(w, dr, w->cubes.inverseGC[4],
		w->core.width - offsetX, offsetY,
		offsetX, w->core.height - offsetY);*/

	sumX = w->cubes.sizeX * w->cubes.offset.x + 4;
	sumY = w->cubes.sizeY * w->cubes.offset.y + 4;
	if (w->cubes.vertical) {
		sumZ = offsetY;
		dz = sumY;
	} else {
		sumZ = offsetX;
		dz = sumX;
	}
	for (k = 0; k < w->cubes.sizeZ; k++) {
		if (w->cubes.vertical) {
			draw3DFrame(w, dr, focus,
				offsetX, sumZ + k, sumX, sumY);
			if (k > 0) {
				FILLRECTANGLE(w, dr, (focus) ?
					w->cubes.frameGC : w->cubes.inverseGC[2],
					offsetX, sumZ + k - 1,
					sumX, 1);
			}
		} else {
			draw3DFrame(w, dr, focus,
				sumZ + k, offsetY, sumX, sumY);
			if (k > 0) {
				FILLRECTANGLE(w, dr, (focus) ?
					w->cubes.frameGC : w->cubes.inverseGC[2],
					sumZ + k - 1, offsetY,
					1, sumY);
			}
		}
		sumZ += dz;
	}
}

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

static void
moveNoBlocks(CubesWidget w)
{
	setPuzzle(w, ACTION_IGNORE);
}

static void
moveBlocks(CubesWidget w, int from, int fast)
{
	int tempBlock;
#ifdef USE_SOUND
	int oldSpace;

	oldSpace = w->cubes.spacePosition;
#endif

	tempBlock = w->cubes.blockOfPosition[from];
	if (fast != INSTANT && w->cubes.delay > 0) {
		int i = w->cubes.currentRow[0] = toColumn(w, from);
		int j = w->cubes.currentRow[1] = toRow(w, from);
		int k = w->cubes.currentRow[2] = toStack(w, from);

		int l = i + w->cubes.sizeX *
			j - w->cubes.spacePosition % w->cubes.sizeRect;
		/* int m = i + w->cubes.sizeX * j +
			w->cubes.sizeRect * k - w->cubes.spacePosition; */
		/* Order important if w->cubes.sizeX = 1 */

		w->cubes.currentPosition = from;
		if (l % w->cubes.sizeX == 0 && k == toStack(w, w->cubes.spacePosition)) {
			if (l < 0) {
				animateSlide(w, -l / w->cubes.sizeX, BOTTOM,
					fast, False);
			} else if (l > 0) {
				animateSlide(w, l / w->cubes.sizeX, TOP, fast,
					False);
			}
		} else if (l / w->cubes.sizeX == 0 &&
				j == toRow(w, w->cubes.spacePosition) &&
				k == toStack(w, w->cubes.spacePosition)) {
			if (l < 0) {
				animateSlide(w, -l, RIGHT, fast, False);
			} else {	/* (l > 0) */
				animateSlide(w, l, LEFT, fast, False);
			}
		} else {
			w->cubes.blockOfPosition[from] =
				w->cubes.blockOfPosition[w->cubes.spacePosition];
			w->cubes.blockOfPosition[w->cubes.spacePosition] = tempBlock;
			animateMove(w, from, fast);
		}
		w->cubes.currentPosition = -1;
	} else {
		w->cubes.blockOfPosition[from] =
			w->cubes.blockOfPosition[w->cubes.spacePosition];
		w->cubes.blockOfPosition[w->cubes.spacePosition] = tempBlock;
		drawBlock(w, w->cubes.spacePosition, False, False, FALSE, 0, 0);
		w->cubes.spacePosition = from;
		drawBlock(w, w->cubes.spacePosition, True, True, FALSE, 0, 0);
	}
#ifdef USE_SOUND
	if (w->cubes.sound) {
		playSound(((ABS(from - oldSpace) >=
			w->cubes.sizeRect) ? DRIPSOUND : BUMPSOUND));
	}
#endif
}

static int
moveBlocksDir(CubesWidget w, int direction, int fast)
{
	switch (direction) {
	case TOP:
		if (toRow(w, w->cubes.spacePosition) < w->cubes.sizeY - 1) {
			moveBlocks(w, w->cubes.spacePosition +
				w->cubes.sizeX, fast);
			return TRUE;
		}
		break;
	case RIGHT:
		if (toColumn(w, w->cubes.spacePosition) > 0) {
			moveBlocks(w, w->cubes.spacePosition - 1, fast);
			return TRUE;
		}
		break;
	case BOTTOM:
		if (toRow(w, w->cubes.spacePosition) > 0) {
			moveBlocks(w, w->cubes.spacePosition -
				w->cubes.sizeX, fast);
			return TRUE;
		}
		break;
	case LEFT:
		if (toColumn(w, w->cubes.spacePosition) < w->cubes.sizeX - 1) {
			moveBlocks(w, w->cubes.spacePosition + 1, fast);
			return TRUE;
		}
		break;
	case INWARDS:
		if (toStack(w, w->cubes.spacePosition) < w->cubes.sizeZ - 1) {
			moveBlocks(w, w->cubes.spacePosition +
				w->cubes.sizeRect, fast);
			return TRUE;
		}
		break;
	case OUTWARDS:
		if (toStack(w, w->cubes.spacePosition) > 0) {
			moveBlocks(w, w->cubes.spacePosition -
				w->cubes.sizeRect, fast);
			return TRUE;
		}
		break;
	default:
		{
			char *buf;

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

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


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

	if (control) {
		reason = ACTION_IGNORE;
		switch (direction) {
		case TOP:
			reason = ACTION_INCY;
			break;
		case RIGHT:
			reason = ACTION_INCX;
			break;
		case BOTTOM:
			if (w->cubes.sizeY <= MIN_BLOCKS)
				return FALSE;
			reason = ACTION_DECY;
			break;
		case LEFT:
			if (w->cubes.sizeX <= MIN_BLOCKS)
				return FALSE;
			reason = ACTION_DECX;
			break;
		case INWARDS:
			if (w->cubes.sizeZ <= MIN_BLOCKS)
				return FALSE;
			reason = ACTION_DECZ;
			break;
		case OUTWARDS:
			reason = ACTION_INCZ;
			break;
		default:
			{
				char *buf;

				intCat(&buf, "movePuzzle: direction ",
					direction);
				DISPLAY_WARNING(buf);
				free(buf);
				setPuzzle(w, reason);
				return FALSE;
			}
		}
		setPuzzle(w, reason);
		return TRUE;
	}
	if (checkSolved(w)) {
		moveNoBlocks(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
exchangeBlocks(CubesWidget w, int pos1, int pos2)
{
	int tempBlock;

	if (w->cubes.blockOfPosition[pos1] <= 0)
		return FALSE;
	else if (w->cubes.blockOfPosition[pos2] <= 0)
		return FALSE;
	tempBlock = w->cubes.blockOfPosition[pos1];
	w->cubes.blockOfPosition[pos1] = w->cubes.blockOfPosition[pos2];
	w->cubes.blockOfPosition[pos2] = tempBlock;
	return TRUE;
}

static void
discreteMoves(CubesWidget w, int from, int dir, int fast)
{
	moveBlocks(w, from, fast);
	setPuzzle(w, ACTION_MOVED);
	setMove(&undo, dir);
	flushMoves(w, &redo, FALSE);
}

static int
positionToBlock(CubesWidget w, int x, int y, int *i, int *j, int *k)
{
	if (w->cubes.vertical) {
		*i = (x - w->cubes.delta.x / 2 -
			w->cubes.puzzleOffset.x) / w->cubes.offset.x;
		*j = ((y - w->cubes.delta.y / 2 -
			w->cubes.puzzleOffset.y) % (w->cubes.sizeY *
			w->cubes.offset.y + w->cubes.delta.y + 4)) /
			w->cubes.offset.y;
		*k = (y - w->cubes.delta.y / 2 -
			w->cubes.puzzleOffset.y) / (w->cubes.sizeY *
			w->cubes.offset.y + w->cubes.delta.y + 4);
	} else {
		*i = ((x - w->cubes.delta.x / 2 -
			w->cubes.puzzleOffset.x) % (w->cubes.sizeX *
			w->cubes.offset.x + w->cubes.delta.x + 4)) /
			w->cubes.offset.x;
		*j = (y - w->cubes.delta.y / 2 -
			w->cubes.puzzleOffset.y) / w->cubes.offset.y;
		*k = (x - w->cubes.delta.x / 2 -
			w->cubes.puzzleOffset.x) / (w->cubes.sizeX *
			w->cubes.offset.x + w->cubes.delta.y + 4);
	}
	if (*i >= 0 && *j >= 0 && *k >= 0 && *i < w->cubes.sizeX &&
			*j < w->cubes.sizeY && *k < w->cubes.sizeZ)
		return toPosition(w, *i, *j, *k);
	else
		return -1;
}

static int
movableBlock(CubesWidget w)
{
	int i, j, k, l, m;

	i = w->cubes.currentRow[0];
	j = w->cubes.currentRow[1];
	k = w->cubes.currentRow[2];
	l = i + w->cubes.sizeX * j - w->cubes.spacePosition % w->cubes.sizeRect;
	m = i + w->cubes.sizeX * j + w->cubes.sizeRect * k - w->cubes.spacePosition;

	/* Order important if w->cubes.sizeX = 1 */
	if (l % w->cubes.sizeX == 0 && k == toStack(w, w->cubes.spacePosition)) {
		if (l == 0) {
			return SPACE;
		}
	} else if (!(l / w->cubes.sizeX == 0 &&
			j == toRow(w, w->cubes.spacePosition) &&
			k == toStack(w, w->cubes.spacePosition)) &&
			(m % w->cubes.sizeRect != 0)) {
		return BLOCKED;
	}
	return 0;
}

static void
selectBlocks(CubesWidget w)
{
	int i, j, k, l, m, n;
	char *buf;

	i = w->cubes.currentRow[0];
	j = w->cubes.currentRow[1];
	k = w->cubes.currentRow[2];
	l = i + w->cubes.sizeX * j - w->cubes.spacePosition % w->cubes.sizeRect;
	m = i + w->cubes.sizeX * j + w->cubes.sizeRect * k - w->cubes.spacePosition;

	/* Order important if w->cubes.sizeX = 1 */
	if (l % w->cubes.sizeX == 0 && k == toStack(w, w->cubes.spacePosition)) {
		if (l < 0) {
			if (w->cubes.delay > 0) {
				animateSlide(w, -l / w->cubes.sizeX, BOTTOM,
					NORMAL, True);
#ifdef USE_SOUND
				if (w->cubes.sound) {
					playSound(BUMPSOUND);
				}
#endif
			} else {
				for (n = 1; n <= -l / w->cubes.sizeX; n++)
					discreteMoves(w,
						w->cubes.spacePosition - w->cubes.sizeX,
						BOTTOM, NORMAL);
			}
		} else if (l > 0) {
			if (w->cubes.delay > 0) {
				animateSlide(w, l / w->cubes.sizeX, TOP, NORMAL,
					True);
#ifdef USE_SOUND
				if (w->cubes.sound) {
					playSound(BUMPSOUND);
				}
#endif
			} else {
				for (n = 1; n <= l / w->cubes.sizeX; n++)
					discreteMoves(w,
						w->cubes.spacePosition + w->cubes.sizeX,
						TOP, NORMAL);
			}
		} else {	/* (l == 0) */
			intCat(&buf, "selectBlocks: ", SPACE);
			DISPLAY_WARNING(buf);
			free(buf);
			return;
		}
	} else if (l / w->cubes.sizeX == 0 &&
			j == toRow(w, w->cubes.spacePosition) &&
			k == toStack(w, w->cubes.spacePosition)) {
		if (l < 0) {
			if (w->cubes.delay > 0) {
				animateSlide(w, -l, RIGHT, NORMAL, True);
#ifdef USE_SOUND
				if (w->cubes.sound) {
					playSound(BUMPSOUND);
				}
#endif
			} else {
				for (n = 1; n <= -l % w->cubes.sizeX; n++)
					discreteMoves(w,
						w->cubes.spacePosition - 1,
						RIGHT, NORMAL);
			}
		} else {	/* (l > 0) */
			if (w->cubes.delay > 0) {
				animateSlide(w, l, LEFT, NORMAL, True);
#ifdef USE_SOUND
				if (w->cubes.sound) {
					playSound(BUMPSOUND);
				}
#endif
			} else {
				for (n = 1; n <= l % w->cubes.sizeX; n++)
					discreteMoves(w,
						w->cubes.spacePosition + 1,
						LEFT, NORMAL);
			}
		}
	} else if (m % w->cubes.sizeRect == 0) {
		if (m < 0) {
			for (n = 1; n <= -toStack(w, m); n++) {
				discreteMoves(w,
					w->cubes.spacePosition - w->cubes.sizeRect,
					OUTWARDS, (w->cubes.delay <= 0) ?
					INSTANT : NORMAL);
			}
		} else {	/* (m > 0) */
			for (n = 1; n <= toStack(w, m); n++) {
				discreteMoves(w,
					w->cubes.spacePosition + w->cubes.sizeRect,
					INWARDS, (w->cubes.delay <= 0) ?
					INSTANT : NORMAL);
			}
		}
	} else {
		intCat(&buf, "selectBlocks: ", BLOCKED);
		DISPLAY_WARNING(buf);
		free(buf);
		return;
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
resetBlocks(CubesWidget w)
{
	int i, j, switch1 = 0, switch2 = 0;

	/*reformatPuzzle(w, 0);*/
	w->cubes.sizeRect = w->cubes.sizeX * w->cubes.sizeY;
	w->cubes.sizeBlock = w->cubes.sizeRect * w->cubes.sizeZ;
	if (w->cubes.blockOfPosition)
		free(w->cubes.blockOfPosition);
	if (!(w->cubes.blockOfPosition = (int *)
			malloc(sizeof (int) * (size_t) w->cubes.sizeBlock))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	if (startPosition)
		free(startPosition);
	if (!(startPosition = (int *)
			malloc(sizeof (int) * (size_t) w->cubes.sizeBlock))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	/*w->cubes.spacePosition = w->cubes.sizeBlock - 1;
	w->cubes.blockOfPosition[w->cubes.sizeBlock - 1] = 0;
	for (i = 1; i < w->cubes.sizeBlock; i++)
		w->cubes.blockOfPosition[i - 1] = i;*/
	/* makes puzzle too trivial
	for (i = 0; i < w->cubes.sizeBlock; i++)
		w->cubes.blockOfPosition[i] = w->cubes.sizeBlock - i;
	w->cubes.spacePosition = 0;
	w->cubes.blockOfPosition[0] = 0;*/
	for (i = 0; i < w->cubes.sizeBlock; i++) {
		j = toInvPosition(w, toColumn(w, i), toRow(w, i), toStack(w, i)) + 1;
		w->cubes.blockOfPosition[i] = j;
		if (j == w->cubes.sizeBlock - 2)
			switch1 = i;
		else if (j == w->cubes.sizeBlock - 1)
			switch2 = i;
	}
	w->cubes.spacePosition = w->cubes.sizeBlock - 1;
	w->cubes.blockOfPosition[w->cubes.sizeBlock - 1] = 0;
	if (decideSwap(w)) {
		i = w->cubes.blockOfPosition[switch1];
		w->cubes.blockOfPosition[switch1] =
			w->cubes.blockOfPosition[switch2];
		w->cubes.blockOfPosition[switch2] = i;
	}
	if ((w->cubes.sizeX == 2 && w->cubes.sizeY == 2 &&
			w->cubes.sizeZ == 1) ||
			(w->cubes.sizeX == 2 && w->cubes.sizeY == 1 &&
			w->cubes.sizeZ == 2) ||
			(w->cubes.sizeX == 1 && w->cubes.sizeY == 2 &&
			w->cubes.sizeZ == 2)) {
		w->cubes.spacePosition = 0;
		w->cubes.blockOfPosition[0] = 0;
		w->cubes.blockOfPosition[1] = 3;
		w->cubes.blockOfPosition[2] = 2;
		w->cubes.blockOfPosition[3] = 1;
	}
	if ((w->cubes.sizeX > 1 && w->cubes.sizeY == 1 &&
			w->cubes.sizeZ == 1) ||
			(w->cubes.sizeX == 1 && w->cubes.sizeY > 1 &&
			w->cubes.sizeZ == 1) ||
			(w->cubes.sizeX == 1 && w->cubes.sizeY == 1 &&
			w->cubes.sizeZ > 1)) {
		int max = (w->cubes.sizeX > w->cubes.sizeY) ?
			w->cubes.sizeX : (w->cubes.sizeY > w->cubes.sizeZ) ?
			w->cubes.sizeY : w->cubes.sizeZ;

		w->cubes.spacePosition = 0;
		w->cubes.blockOfPosition[0] = 0;
		for (i = 1; i < max; i++)
			w->cubes.blockOfPosition[i] = i;
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->cubes.currentPosition = -1;
	w->cubes.started = False;
}

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

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &sizeX) != 1) {
		(void) printf("corrupt record, expecting an integer for sizeX\n");
		return;
	}
	if (sizeX >= MIN_BLOCKS) {
		for (i = w->cubes.sizeX; i < sizeX; i++) {
			setPuzzle(w, ACTION_INCX);
		}
		for (i = w->cubes.sizeX; i > sizeX; i--) {
			setPuzzle(w, ACTION_DECX);
		}
	} else {
		stringCat(&buf1, name, " corrupted: sizeX ");
		intCat(&buf2, buf1, sizeX);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_BLOCKS);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &sizeY) != 1) {
		(void) printf("corrupt record, expecting an integer for sizeY\n");
		return;
	}
	if (sizeY >= MIN_BLOCKS) {
		for (i = w->cubes.sizeY; i < sizeY; i++) {
			setPuzzle(w, ACTION_INCY);
		}
		for (i = w->cubes.sizeY; i > sizeY; i--) {
			setPuzzle(w, ACTION_DECY);
		}
	} else {
		stringCat(&buf1, name, " corrupted: sizeY ");
		intCat(&buf2, buf1, sizeY);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_BLOCKS);
		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", &sizeZ) != 1) {
		(void) printf("corrupt record, expecting an integer for sizeZ\n");
		return;
	}
	if (sizeZ >= MIN_BLOCKS) {
		for (i = w->cubes.sizeZ; i < sizeZ; i++) {
			setPuzzle(w, ACTION_INCZ);
		}
		for (i = w->cubes.sizeZ; i > sizeZ; i--) {
			setPuzzle(w, ACTION_DECZ);
		}
	} else {
		stringCat(&buf1, name, " corrupted: sizeZ ");
		intCat(&buf2, buf1, sizeZ);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_BLOCKS);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
#ifdef WINVER
	resetBlocks(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: sizeX %d, sizeY %d, sizeZ %d, moves %d\n",
		name, sizeX, sizeY, sizeZ, moves);
}

static void
getBlocks(CubesWidget 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->cubes.cheat = True; /* Assume the worst. */
}

static void
writeBlocks(CubesWidget 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, "sizeX%c %d\n", SYMBOL, w->cubes.sizeX);
	(void) fprintf(fp, "sizeY%c %d\n", SYMBOL, w->cubes.sizeY);
	(void) fprintf(fp, "sizeZ%c %d\n", SYMBOL, w->cubes.sizeZ);
	(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
undoBlocks(CubesWidget w)
{
	if (madeMoves(&undo) &&
			w->cubes.currentPosition < 0) {
		int direction;

		getMove(&undo, &direction);
		setMove(&redo, direction);
		direction = (direction < 4) ? (direction + 2) % 4 :
			((direction == INWARDS) ? OUTWARDS : INWARDS);
		if (moveBlocksDir(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
redoBlocks(CubesWidget w)
{
	if (madeMoves(&redo) &&
			w->cubes.currentPosition < 0) {
		int direction;

		getMove(&redo, &direction);
		setMove(&undo, direction);
		if (moveBlocksDir(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
clearBlocks(CubesWidget w)
{
	if (w->cubes.currentPosition >= 0)
		return;
	resetBlocks(w);
	drawAllBlocks(w);
	setPuzzle(w, ACTION_RESET);
}

static void
randomizeBlocks(CubesWidget w)
{
	if (w->cubes.currentPosition >= 0)
		return;
	w->cubes.cheat = False;
	/* First interchange blocks an even number of times */
	/* Must be at least 3x2 or 2x2x2 */
	if ((w->cubes.sizeX > 2 && (w->cubes.sizeY > 1 || w->cubes.sizeZ > 1)) ||
			(w->cubes.sizeY > 2 && (w->cubes.sizeX > 1 || w->cubes.sizeZ > 1)) ||
			(w->cubes.sizeZ > 2 && (w->cubes.sizeX > 1 || w->cubes.sizeY > 1)) ||
			(w->cubes.sizeX == 2 && w->cubes.sizeY == 2 && w->cubes.sizeZ == 2)) {
		int pos, count = 0;

		for (pos = 0; pos < w->cubes.sizeBlock; pos++) {
			int randomPos = pos;

			while (randomPos == pos) {
				randomPos = NRAND(w->cubes.sizeBlock);
			}
			count += exchangeBlocks(w, pos, randomPos);
		}
		if ((count & 1) &&
				!exchangeBlocks(w, 0, 1) &&
				!exchangeBlocks(w, w->cubes.sizeBlock - 2,
				w->cubes.sizeBlock - 1)) {
			DISPLAY_WARNING("randomizeBlocks: should not get here");
		}
		drawAllBlocks(w);
		FLUSH(w);
	}
	/* randomly position space */
	/* Idea for this came from "puzzle" by Don Bennett, HP Labs */
	{
		int n, s, e;

		s = toColumn(w, w->cubes.spacePosition);
		e = NRAND(w->cubes.sizeX);
		for (n = 0; n < e - s; n++)
			(void) movePuzzleDir(w, LEFT, INSTANT);
		for (n = 0; n < s - e; n++)
			(void) movePuzzleDir(w, RIGHT, INSTANT);
		s = toRow(w, w->cubes.spacePosition);
		e = NRAND(w->cubes.sizeY);
		for (n = 0; n < e - s; n++)
			(void) movePuzzleDir(w, TOP, INSTANT);
		for (n = 0; n < s - e; n++)
			(void) movePuzzleDir(w, BOTTOM, INSTANT);
		s = toStack(w, w->cubes.spacePosition);
		e = NRAND(w->cubes.sizeZ);
		for (n = 0; n < e - s; n++)
			(void) movePuzzleDir(w, OUTWARDS, INSTANT);
		for (n = 0; n < s - e; n++)
			(void) movePuzzleDir(w, INWARDS, INSTANT);
		flushMoves(w, &undo, TRUE);
		flushMoves(w, &redo, FALSE);
		setPuzzle(w, ACTION_RANDOMIZE);
	}
#if 0
	/* Now move the space around randomly */
	if (w->cubes.sizeX > 1 || w->cubes.sizeY > 1 || w->cubes.sizeZ > 1) {
		int big = w->cubes.sizeBlock + NRAND(2);
		int lastDirection = -1;
		int randomDirection;

		setPuzzle(w, ACTION_RESET);
#ifdef DEBUG
		big = 3;
#endif
		if (big > 1000)
			big = 1000;
		while (big--) {
			randomDirection = NRAND(COORD);
#ifdef DEBUG
			Sleep(1);
#endif
			if ((randomDirection < 4 && (randomDirection + 2) % 4 != lastDirection) ||
					(randomDirection >= 4 && randomDirection + ((randomDirection & 1) ? -1 : 1) != lastDirection) ||
					(w->cubes.sizeX == 1 && w->cubes.sizeY == 1) ||
					(w->cubes.sizeY == 1 && w->cubes.sizeZ == 1) ||
					(w->cubes.sizeZ == 1 && w->cubes.sizeX == 1)) {
				if (movePuzzleDir(w, randomDirection, INSTANT))
					lastDirection = randomDirection;
				else
					big++;
			}
		}
		flushMoves(w, &undo, TRUE);
		flushMoves(w, &redo, FALSE);
		setPuzzle(w, ACTION_RANDOMIZE);
	}
#endif
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
solveBlocks(CubesWidget w)
{
	if (checkSolved(w) || w->cubes.currentPosition >= 0)
		return;
	if (w->cubes.sizeX == 1 || w->cubes.sizeY == 1 ||
			w->cubes.sizeZ == 1) {
		solveSomeBlocks(w);
	}
#ifndef TRY
	else {
		setPuzzle(w, ACTION_SOLVE_MESSAGE);
	}
#endif
}

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

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

static void
soundBlocks(CubesWidget w)
{
	w->cubes.sound = !w->cubes.sound;
	setPuzzle(w, ACTION_SOUND);
}

#define BRIGHT_FACTOR 0.8
#define DARK_FACTOR 0.75

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

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

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

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

	w->cubes.sizeX = (int) GetPrivateProfileInt(SECTION,
		"sizeX", DEFAULT_BLOCKSX, INIFILE);
	w->cubes.sizeY = (int) GetPrivateProfileInt(SECTION,
		"sizeY", DEFAULT_BLOCKSY, INIFILE);
	w->cubes.sizeZ = (int) GetPrivateProfileInt(SECTION,
		"sizeZ", DEFAULT_BLOCKSZ, INIFILE);
	w->cubes.base = (int) GetPrivateProfileInt(SECTION,
		"base", DEFAULT_BASE, INIFILE);
	w->cubes.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->cubes.reverse = (BOOL) GetPrivateProfileInt(SECTION,
		"reverseVideo", DEFAULT_REVERSE, INIFILE);
	/* Tan (wheat4) */
	(void) GetPrivateProfileString(SECTION,
		"frameColor", "139 126 102", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->cubes.frameGC = RGB(color.red, color.green, color.blue);
	/* gray75 */
	(void) GetPrivateProfileString(SECTION,
		"blockColor", "191 191 191", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->cubes.blockGC[1] = RGB(color.red, color.green, color.blue);
	w->cubes.blockGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->cubes.blockGC[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->cubes.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->cubes.inverseGC[1] = RGB(color.red, color.green, color.blue);
	w->cubes.inverseGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->cubes.inverseGC[2] = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	w->cubes.inverseGC[3] = RGB(darker(darker(color.red)),
		darker(darker(color.green)), darker(darker(color.blue)));
	w->cubes.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->cubes.picture, szBuf, 80);
	w->cubes.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", DEFAULT_DELAY, INIFILE);
	w->cubes.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", 0, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"bumpSound", BUMPSOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->cubes.bumpSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"dripSound", DRIPSOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->cubes.dripSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->cubes.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->cubes.scoreFile, szBuf, 80);
}

void
destroyPuzzle(CubesWidget w, HBRUSH brush)
{
	if (w->core.memDC) {
		DeleteObject(w->cubes.bufferBlocks);
		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(CubesWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = (w->cubes.colormap == None) ?
		w->cubes.oldColormap : w->cubes.colormap;
	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(CubesWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = (w->cubes.colormap == None) ?
		w->cubes.oldColormap : w->cubes.colormap;

	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(CubesWidget w)
{
	Display *display = XtDisplay(w);
	Window window = XtWindow(w);
	XGCValues values;
	XtGCMask valueMask;

	valueMask = GCForeground | GCBackground;

	if (w->cubes.reverse) {
		values.foreground = w->cubes.foreground;
		values.background = w->cubes.background;
	} else {
		values.foreground = w->cubes.background;
		values.background = w->cubes.foreground;
	}
	if (w->cubes.inverseGC[1])
		XtReleaseGC((Widget) w, w->cubes.inverseGC[1]);
	w->cubes.inverseGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->cubes.mono) {
		values.foreground = brighter(w, (w->cubes.reverse) ?
			w->cubes.foreground : w->cubes.background);
	}
	if (w->cubes.inverseGC[0])
		XtReleaseGC((Widget) w, w->cubes.inverseGC[0]);
	w->cubes.inverseGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->cubes.mono) {
		values.foreground = darker(w, (w->cubes.reverse) ?
			w->cubes.foreground : w->cubes.background);
	}
	if (w->cubes.inverseGC[2])
		XtReleaseGC((Widget) w, w->cubes.inverseGC[2]);
	w->cubes.inverseGC[2] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->cubes.mono) {
		values.foreground = darker(w, darker(w, (w->cubes.reverse) ?
			w->cubes.foreground : w->cubes.background));
	}
	if (w->cubes.inverseGC[3])
		XtReleaseGC((Widget) w, w->cubes.inverseGC[3]);
	w->cubes.inverseGC[3] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->cubes.mono) {
		values.foreground = darker(w, darker(w, darker(w,
			(w->cubes.reverse) ?  w->cubes.foreground :
			w->cubes.background)));
	}
	if (w->cubes.inverseGC[4])
		XtReleaseGC((Widget) w, w->cubes.inverseGC[4]);
	w->cubes.inverseGC[4] = XtGetGC((Widget) w, valueMask, &values);
	if (w->cubes.mono) {
		if (w->cubes.reverse) {
			values.foreground = w->cubes.background;
			values.background = w->cubes.foreground;
		} else {
			values.foreground = w->cubes.foreground;
			values.background = w->cubes.background;
		}
	} else {
		values.foreground = w->cubes.frameColor;
		values.background = w->cubes.foreground;
	}
	if (w->cubes.frameGC)
		XtReleaseGC((Widget) w, w->cubes.frameGC);
	w->cubes.frameGC = XtGetGC((Widget) w, valueMask, &values);
	w->cubes.grainLR = XCreateBitmapFromData(display, window,
		(char *) grain_lr_bits, grain_lr_width, grain_lr_height);
	w->cubes.grainTB = XCreateBitmapFromData(display, window,
		(char *) grain_tb_bits, grain_tb_width, grain_tb_height);
	if (w->cubes.mono) {
		if (w->cubes.reverse) {
			values.foreground = w->cubes.background;
			values.background = w->cubes.foreground;
		} else {
			values.foreground = w->cubes.foreground;
			values.background = w->cubes.background;
		}
	} else {
		values.foreground = w->cubes.blockColor;
		values.background = w->cubes.textColor;
	}

	if (w->cubes.blockGC[1])
		XtReleaseGC((Widget) w, w->cubes.blockGC[1]);
	w->cubes.blockGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->cubes.mono) {
		values.foreground = brighter(w, w->cubes.blockColor);
	}
	if (w->cubes.blockGC[0])
		XtReleaseGC((Widget) w, w->cubes.blockGC[0]);
	w->cubes.blockGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->cubes.mono) {
		values.foreground = darker(w, w->cubes.blockColor);
	}
	if (w->cubes.blockGC[2])
		XtReleaseGC((Widget) w, w->cubes.blockGC[2]);
	w->cubes.blockGC[2] = XtGetGC((Widget) w, valueMask, &values);
	if (w->cubes.mono) {
		if (w->cubes.reverse) {
			values.foreground = w->cubes.foreground;
			values.background = w->cubes.background;
		} else {
			values.foreground = w->cubes.background;
			values.background = w->cubes.foreground;
		}
	} else {
		values.foreground = w->cubes.textColor;
		values.background = w->cubes.blockColor;
	}
	if (w->cubes.textGC)
		XtReleaseGC((Widget) w, w->cubes.textGC);
	w->cubes.textGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->cubes.fontInfo)
		XSetFont(XtDisplay(w), w->cubes.textGC,
			w->cubes.fontInfo->fid);
}

static Boolean
setValuesPuzzle(Widget current, Widget request, Widget renew)
{
	CubesWidget c = (CubesWidget) current, w = (CubesWidget) renew;
	Boolean redraw = False;
	Boolean redrawBlocks = False;

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

		w->cubes.menu = ACTION_IGNORE;
		switch (menu) {
		case ACTION_GET:
			getBlocks(w);
			break;
		case ACTION_WRITE:
			writeBlocks(w);
			break;
		case ACTION_UNDO:
			undoBlocks(w);
			break;
		case ACTION_REDO:
			redoBlocks(w);
			break;
		case ACTION_CLEAR:
			clearBlocks(w);
			break;
		case ACTION_RANDOMIZE:
			randomizeBlocks(w);
			break;
		case ACTION_SOLVE:
			solveBlocks(w);
			break;
		case ACTION_SPEED:
			speedBlocks(w);
			break;
		case ACTION_SLOW:
			slowBlocks(w);
			break;
		case ACTION_SOUND:
			soundBlocks(w);
			break;
		}
	}
	if (redrawBlocks && !redraw && XtIsRealized(renew) &&
			renew->core.visible) {
		eraseFrame(c, 0);
		if (w->cubes.focus)
			drawFrame(w, 0, True);
		drawAllBlocks(w);
	}
	return (redraw);
}

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

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

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

static void
resizeBlocks(CubesWidget 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->cubes.bufferBlocks[sel] != NULL) {
			DeleteObject(w->cubes.bufferBlocks[sel]);
				w->cubes.bufferBlocks[sel] = NULL;
		}
		if (!hasValue(w->cubes.picture)) {
			if ((w->cubes.bufferBlocks[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->cubes.oldColormap == None) {
		w->cubes.mono = (xgwa.depth < 2 || w->cubes.mono);
		w->cubes.oldColormap = xgwa.colormap;
	}
	for (sel = 0; sel < 2; sel++) {
		if (w->cubes.bufferBlocks[sel] != None) {
			XFreePixmap(display, w->cubes.bufferBlocks[sel]);
			w->cubes.bufferBlocks[sel] = None;
		}
		if ((w->cubes.bufferBlocks[sel] = XCreatePixmap(display,
				window, w->core.width, w->core.height,
				xgwa.depth)) == None) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
#endif
	if (hasValue(w->cubes.picture)) {
#ifdef WINVER
		for (sel = 0; sel < 2; sel++) {
			w->cubes.bufferBlocks[sel] =
				LoadBitmap(w->core.hInstance,
				w->cubes.picture);
		}
#else
		if (w->cubes.image != NULL) {
			destroyImage(&(w->cubes.image),
				&(w->cubes.graphicsFormat));
		}
		if (!getImage(display, window,
				xgwa.visual, w->cubes.oldColormap, xgwa.depth,
				&(w->cubes.image), w->cubes.picture,
				w->cubes.install, &(w->cubes.graphicsFormat),
				&(w->cubes.colormap))) {
			w->cubes.picture = NULL;
		} else if (w->cubes.image == NULL) {
			w->cubes.picture = NULL;
		}
#endif
	}
#ifndef WINVER
	if (!hasValue(w->cubes.picture) &&
			!fixedColors(xgwa.visual, xgwa.depth, w->cubes.install) &&
			w->cubes.colormap == None) {
		w->cubes.colormap = XCreateColormap(display, window,
			xgwa.visual, AllocNone);
	}
	setAllColors(w);
	for (sel = 0; sel < 2; sel++) {
		FILLRECTANGLE(w, w->cubes.bufferBlocks[sel],
			w->cubes.inverseGC[2],
			0, 0, w->core.width, w->core.height);
		if (hasValue(w->cubes.picture)) {
			(void) XPutImage(display, w->cubes.bufferBlocks[sel],
				w->cubes.inverseGC[2], w->cubes.image, 0, 0,
				0, 0,
				MIN(w->cubes.image->width, w->core.width),
				MIN(w->cubes.image->height, w->core.height));
		}
	}
#endif
	if (!hasValue(w->cubes.picture)) {
		drawAllBufferedBlocks(w);
	}
}

void
resizePuzzle(CubesWidget w)
{
	int frameThickness;
#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
	frameThickness = MIN(16, MIN(w->core.width, w->core.height) / 16);
	w->cubes.delta.x = 1;
	w->cubes.delta.y = 1;
        
	w->cubes.offset.x = MAX(((int) w->core.width - 2 * frameThickness - 4) /
		w->cubes.sizeX, 0);
	w->cubes.offset.y = MAX(((int) w->core.height - 2 * frameThickness - 4) /
		w->cubes.sizeY, 0);
	if (w->cubes.offset.y >= w->cubes.offset.x) {
		w->cubes.vertical = TRUE;
		w->cubes.offset.y = MAX((((int) w->core.height - 2 * frameThickness -
			5 * w->cubes.sizeZ + 1) / w->cubes.sizeZ) /
			w->cubes.sizeY, 0);
	} else {
		w->cubes.vertical = FALSE;
		w->cubes.offset.x = MAX((((int) w->core.width - 2 * frameThickness -
			5 * w->cubes.sizeZ + 1) / w->cubes.sizeZ) /
			w->cubes.sizeX, 0);
	}
	w->cubes.faceSize.x = w->cubes.offset.x * w->cubes.sizeX + 4;
	w->cubes.faceSize.y = w->cubes.offset.y * w->cubes.sizeY + 4;
	if (w->cubes.vertical) {
		w->cubes.puzzleSize.x = w->cubes.faceSize.x;
		w->cubes.puzzleSize.y = w->cubes.faceSize.y *
			w->cubes.sizeZ + w->cubes.sizeZ - 1;
	} else {
		w->cubes.puzzleSize.x = w->cubes.faceSize.x *
			w->cubes.sizeZ + w->cubes.sizeZ - 1;
		w->cubes.puzzleSize.y = w->cubes.faceSize.y;
	}
	w->cubes.puzzleOffset.x = ((int) w->core.width -
		w->cubes.puzzleSize.x) / 2;
	w->cubes.puzzleOffset.y = ((int) w->core.height -
		w->cubes.puzzleSize.y) / 2;
	w->cubes.blockSize.x = MAX(w->cubes.offset.x - w->cubes.delta.x, 0);
	w->cubes.blockSize.y = MAX(w->cubes.offset.y - w->cubes.delta.y, 0);
}

#ifndef WINVER
static
#endif
void
sizePuzzle(CubesWidget w)
{
	resetBlocks(w);
	resizePuzzle(w);
}

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

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

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

	if (!w->core.visible)
		return;
#endif
	resizeBlocks(w);
	eraseFrame(w, 0);
	drawFrame(w, 0, w->cubes.focus);
	drawAllBlocks(w);
}

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

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

	pos = positionToBlock(w, x, y, &i, &j, &k);
	if (pos >= 0) {
		if (checkSolved(w)) {
			moveNoBlocks(w);
			w->cubes.currentPosition = -1;
			return;
		}
		w->cubes.currentPosition = pos;
		w->cubes.currentRow[0] = i;
		w->cubes.currentRow[1] = j;
		w->cubes.currentRow[2] = k;
		rowType = movableBlock(w);
		if (rowType < 0) {
			drawBlock(w, w->cubes.currentPosition,
				rowType == SPACE, False, TRUE, 0, 0);
			FLUSH(w);
			Sleep(100);
			drawBlock(w, w->cubes.currentPosition,
				True, True, TRUE, 0, 0);
			if (rowType != SPACE)
				drawBlock(w, w->cubes.currentPosition,
					False, False, FALSE, 0, 0);
			setPuzzle(w, rowType);
			w->cubes.currentPosition = -1;
			return;
		}
		drawBlock(w, w->cubes.currentPosition,
			False, False, TRUE, 0, 0);
	} else
		w->cubes.currentPosition = -1;
}

#ifndef WINVER
static
#endif
void
releasePuzzle(CubesWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	if (w->cubes.currentPosition < 0)
		return;
	drawBlock(w, w->cubes.currentPosition, True, True, TRUE, 0, 0);
	drawBlock(w, w->cubes.currentPosition, False, False, FALSE, 0, 0);
	selectBlocks(w);
	w->cubes.currentPosition = -1;
}

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

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

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

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

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

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

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

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

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

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

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

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

#ifndef WINVER
static
#endif
void
enterPuzzle(CubesWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->cubes.focus = True;
	drawFrame(w, 0, w->cubes.focus);
#ifndef WINVER
	if (w->cubes.colormap != None)
		XInstallColormap(XtDisplay(w), w->cubes.colormap);
#endif
}

#ifndef WINVER
static
#endif
void
leavePuzzle(CubesWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->cubes.focus = False;
	drawFrame(w, 0, w->cubes.focus);
#ifndef WINVER
	if (w->cubes.colormap != None)
		XInstallColormap(XtDisplay(w), w->cubes.oldColormap);
#endif
}

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

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

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

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

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

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