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

/* Methods file for Oct */

#include "file.h"
#include "rngs.h"
#include "sound.h"
#include "OctP.h"
#include "Oct2dP.h"
#ifdef HAVE_OPENGL
#include "OctGLP.h"
#endif

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

#define SECTION "setup"

static const char *faceColorString[MAX_FACES] =
{
	"255 0 0",
	"0 0 255",
	"255 255 255",
	"255 0 255",
	"255 165 0",
	"255 192 203",
	"0 255 0",
	"255 255 0"
};

static const char faceColorChar[MAX_FACES] =
{'R', 'B', 'W', 'M', 'O', 'P', 'G', 'Y'};
#else

#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 void resizePuzzle(OctWidget w);
static void sizePuzzle(OctWidget w);*/
static Boolean setValuesPuzzle(Widget current, Widget request, Widget renew);
static void destroyPuzzle(Widget old);
static void initializePuzzle(Widget request, Widget renew);
/*static void exposePuzzle(Widget renew, XEvent *event, Region region);*/

OctClassRec octClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Oct",		/* class name */
		sizeof (OctRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) initializePuzzle,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		NULL,		/* actions */
		0,		/* num actions */
		NULL,		/* resources */
		0,		/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		(XtWidgetProc) destroyPuzzle,	/* destroy */
		NULL,		/* resize */
		NULL,		/* 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 */
		NULL,		/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	}
};

WidgetClass octWidgetClass = (WidgetClass) & octClassRec;

void
setPuzzle(OctWidget w, int reason)
{
	octCallbackStruct cb;

	cb.reason = reason;
	cb.style = -1;
	XtCallCallbacks((Widget) w, (char *) XtNselectCallback, &cb);
}

static void
setPuzzleMove(OctWidget w, int reason, int face, int position,
		int direction, int style, int control, int fast)
{
	octCallbackStruct cb;

	cb.reason = reason;
	cb.face = face;
	cb.position = position;
	cb.direction = direction;
	cb.style = style;
	cb.control = control;
	cb.fast = fast;
	XtCallCallbacks((Widget) w, (char *) XtNselectCallback, &cb);
}
#endif

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

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

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

#define NOTDIR(x) ((x==CW)?CCW:CW)

typedef struct _RowNextP3 {
	int viewChanged, face, direction;
} RowNextP3;

static int directionNextFace[COORD][MAX_FACES] =
{
	{-1, 0, -1, 0, -1, 4, -1, 4},
	{6, 5, 1, 0, 2, 1, 5, 4},
	{1, -1, 1, -1, 5, -1, 5, -1},
	{1, 5, 4, 2, 5, 1, 0, 6},
	{-1, 2, -1, 2, -1, 6, -1, 6},
	{3, 2, 4, 7, 7, 6, 0, 3},
	{3, -1, 3, -1, 7, -1, 7, -1},
	{6, 0, 3, 7, 2, 4, 7, 3}
};

static int directionNextFaceP3[COORD][MAX_FACES] =
{
	{-1, 6, -1, 6, -1, 2, -1, 2},
	{5, 6, 0, 1, 1, 2, 4, 5},
	{5, -1, 5, -1, 1, -1, 1, -1},
	{2, 4, 5, 1, 6, 0, 1, 5},
	{-1, 4, -1, 4, -1, 0, -1, 0},
	{2, 3, 7, 4, 6, 7, 3, 0},
	{7, -1, 7, -1, 3, -1, 3, -1},
	{7, 3, 0, 6, 3, 7, 4, 2}
};

static OctLoc rowToRotate[MAX_FACES][COORD] =
{
	{
		{IGNORE_DIR, IGNORE_DIR},
		{1, CW},
		{2, CW},
		{3, CW},
		{IGNORE_DIR, IGNORE_DIR},
		{1, CCW},
		{2, CCW},
		{3, CCW}
	},
	{
		{3, CCW},
		{0, CCW},
		{IGNORE_DIR, IGNORE_DIR},
		{2, CW},
		{3, CW},
		{0, CW},
		{IGNORE_DIR, IGNORE_DIR},
		{2, CCW}
	},
	{
		{IGNORE_DIR, IGNORE_DIR},
		{3, CCW},
		{0, CCW},
		{1, CCW},
		{IGNORE_DIR, IGNORE_DIR},
		{3, CW},
		{0, CW},
		{1, CW}
	},
	{
		{1, CW},
		{2, CW},
		{IGNORE_DIR, IGNORE_DIR},
		{0, CCW},
		{1, CCW},
		{2, CCW},
		{IGNORE_DIR, IGNORE_DIR},
		{0, CW}
	},
	{
		{IGNORE_DIR, IGNORE_DIR},
		{3, CCW},
		{2, CCW},
		{1, CCW},
		{IGNORE_DIR, IGNORE_DIR},
		{3, CW},
		{2, CW},
		{1, CW}
	},
	{
		{1, CW},
		{0, CW},
		{IGNORE_DIR, IGNORE_DIR},
		{2, CCW},
		{1, CCW},
		{0, CCW},
		{IGNORE_DIR, IGNORE_DIR},
		{2, CW}
	},
	{
		{IGNORE_DIR, IGNORE_DIR},
		{1, CW},
		{0, CW},
		{3, CW},
		{IGNORE_DIR, IGNORE_DIR},
		{1, CCW},
		{0, CCW},
		{3, CCW}
	},
	{
		{3, CCW},
		{2, CCW},
		{IGNORE_DIR, IGNORE_DIR},
		{0, CW},
		{3, CW},
		{2, CW},
		{IGNORE_DIR, IGNORE_DIR},
		{0, CCW}
	}
};
static RowNextP3 slideNextRowP3[MAX_SIDES][COORD] =
{
	{
		{IGNORE_DIR, IGNORE_DIR, IGNORE_DIR},
		{TRUE, 2, TR},
		{FALSE, 1, BR},
		{FALSE, 1, BOTTOM},
		{IGNORE_DIR, IGNORE_DIR, IGNORE_DIR},
		{FALSE, 3, BOTTOM},
		{FALSE, 3, BL},
		{TRUE, 2, TL}
	},
	{
		{FALSE, 0, TL},
		{TRUE, 1, BL},
		{IGNORE_DIR, IGNORE_DIR, IGNORE_DIR},
		{TRUE, 1, TL},
		{FALSE, 2, BL},
		{FALSE, 2, LEFT},
		{IGNORE_DIR, IGNORE_DIR, IGNORE_DIR},
		{FALSE, 0, LEFT}
	},
	{
		{IGNORE_DIR, IGNORE_DIR, IGNORE_DIR},
		{FALSE, 1, TOP},
		{FALSE, 1, TR},
		{TRUE, 0, BR},
		{IGNORE_DIR, IGNORE_DIR, IGNORE_DIR},
		{TRUE, 0, BL},
		{FALSE, 3, TL},
		{FALSE, 3, TOP},
	},
	{
		{FALSE, 0, TR},
		{FALSE, 0, RIGHT},
		{IGNORE_DIR, IGNORE_DIR, IGNORE_DIR},
		{FALSE, 2, RIGHT},
		{FALSE, 2, BR},
		{TRUE, 3, TR},
		{IGNORE_DIR, IGNORE_DIR, IGNORE_DIR},
		{TRUE, 3, BR}
	}
};
static int reverseP3[MAX_SIDES][COORD] =
{
	{IGNORE_DIR, FALSE, FALSE, TRUE, IGNORE_DIR, TRUE, TRUE, TRUE},
	{TRUE, TRUE, IGNORE_DIR, FALSE, FALSE, TRUE, IGNORE_DIR, TRUE},
	{IGNORE_DIR, TRUE, TRUE, TRUE, IGNORE_DIR, FALSE, FALSE, TRUE},
	{FALSE, TRUE, IGNORE_DIR, TRUE, TRUE, TRUE, IGNORE_DIR, FALSE}
};
static int rotateOrientP3[MAX_SIDES][COORD] =
{
	{IGNORE_DIR, 11, 7, 2, IGNORE_DIR, 10, 5, 1},
	{11, 7, IGNORE_DIR, 5, 1, 2, IGNORE_DIR, 10},
	{IGNORE_DIR, 10, 5, 1, IGNORE_DIR, 11, 7, 2},
	{1, 2, IGNORE_DIR, 10, 11, 7, IGNORE_DIR, 5}
};

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

static void
checkPieces(OctWidget w)
{
	char *buf1 = NULL, *buf2 = NULL;

	if (w->oct.size < MIN_FACETS) {
		intCat(&buf1,
			"Number of triangles on an edge out of bounds, use at least ",
			MIN_FACETS);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_FACETS);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->oct.size = DEFAULT_FACETS;
	}
	if (w->oct.mode < PERIOD3 || w->oct.mode > BOTH) {
		intCat(&buf1, "Mode is in error, use 3 for Period3, 4 for Period4, 5 for Both, defaulting to ",
			DEFAULT_MODE);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->oct.mode = DEFAULT_MODE;
	}
}

Boolean
checkSolved(OctWidget w)
{
	int face, position;
	OctLoc test = {0, FALSE};

	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < w->oct.sizeSize; position++) {
			if (!position) {
				test.face = w->oct.facetLoc[face][position].face;
				test.rotation = w->oct.facetLoc[face][position].rotation;
			} else if (test.face !=		/* MAX_SIDES * view + face */
					w->oct.facetLoc[face][position].face ||
					(w->oct.orient && test.rotation !=
					w->oct.facetLoc[face][position].rotation))
				return False;
		}
	return True;
}

#ifdef DEBUG

static void
printFacets(OctWidget w)
{
	int face, position, square;

	for (face = 0; face < MAX_SIDES; face++) {
		square = 1;
		for (position = 0; position < w->oct.sizeSize; position++) {
			(void) printf("%d %d  ",
				w->oct.facetLoc[face][position].face,
				w->oct.facetLoc[face][position].rotation);
			if (position == square * square - 1) {
				(void) printf("\n");
				++square;
			}
		}
		(void) printf("\n");
	}
	(void) printf("\n");
}

static void
printFace(OctWidget w)
{
	int position;
	int square = 1;

	for (position = 0; position < w->oct.sizeSize; position++) {
		(void) printf("%d %d  ",
			w->oct.faceLoc[position].face,
			w->oct.faceLoc[position].rotation);
		if (position == square * square - 1) {
			(void) printf("\n");
			++square;
		}
	}
	(void) printf("\n");
}

static void
printRow(OctWidget w, int len, int orient)
{
	int i;

	(void) printf("Row %d:\n", orient);
	for (i = 0; i < len; i++)
		(void) printf("%d %d  ", w->oct.rowLoc[orient][i].face,
			w->oct.rowLoc[orient][i].rotation);
	(void) printf("\n");
}

#endif

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

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

void
toRTT(int position, RTT * rtt)
{
	rtt->row = SQRT(position);
	/* Passing row so there is no sqrt calculation again */
	rtt->trbl = (position - rtt->row * rtt->row) / 2;
	rtt->tlbr = (rtt->row * rtt->row + 2 * rtt->row - position) / 2;
}

int
toPosition(RTT rtt)
{
	return (rtt.row * rtt.row + rtt.row + rtt.trbl - rtt.tlbr);
}

static int
length(OctWidget w, int dir, int h)
{
	if (!(dir % 2) || dir > COORD)
		return (2 * h + 1);
	else
		return (2 * (w->oct.size - h) - 1);
}

static void
drawTriangle(OctWidget w, int face, int position, int offset)
{
	if (w->oct.dim == 2)
		drawTriangle2D((Oct2DWidget) w, face, position, offset);
}

static void
drawTriangleAll(OctWidget w, int face, int position, int offset)
{
	drawTriangle(w, face, position, offset);
#ifdef HAVE_OPENGL
	if (w->oct.dim == 4)
		drawAllPiecesGL((OctGLWidget) w);
#endif
}

void
drawAllPieces(OctWidget w)
{
	int face, position;

#ifdef HAVE_OPENGL
	if (w->oct.dim == 4) {
		drawAllPiecesGL((OctGLWidget) w);
		return;
	}
#endif
	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < w->oct.sizeSize; position++)
			drawTriangle(w, face, position, FALSE);
}

static void
drawFrame(OctWidget w, Boolean focus)
{
	if (w->oct.dim == 2)
		drawFrame2D((Oct2DWidget) w, focus);
/*#ifdef HAVE_OPENGL
	else if (w->oct.dim == 4)
		drawFrameGL((OctGLWidget) w, focus);
#endif*/
}

static void
moveNoPieces(OctWidget w)
{
	setPuzzle(w, ACTION_ILLEGAL);
}

static void
rotateFace(OctWidget w, int view, int side, int direction)
{
	int g, square;
	int i = 0, j = 0, k, i_1, j_1, k_1, position;

	/* Read Face */
	k = -1;
	square = 0;
	for (g = 0; g < w->oct.sizeSize; g++) {
#if 0
		/* This is the old algorithm, its now more efficient */
		k = SQRT(g);
		j = (g - k * k) / 2;
		i = ((k + 1) * (k + 1) - g - 1) / 2;
#endif
		if (square <= g) {
			k++;
			square = (k + 1) * (k + 1);
			j = -1;
			i = k;
		}
		if (((square - g) & 1) == 0)
			i--;
		else
			j++;
		if (direction == CW) {
			k_1 = w->oct.size - 1 - i;
			i_1 = j;
			j_1 = w->oct.size - 1 - k;
		} else {	/* (direction == CCW) */
			k_1 = w->oct.size - 1 - j;
			j_1 = i;
			i_1 = w->oct.size - 1 - k;
		}
		position = k_1 * k_1 + 2 * j_1 + ((j_1 == k_1 - i_1) ? 0 : 1);
		w->oct.faceLoc[position] = w->oct.facetLoc[view * MAX_SIDES + side][g];
	}
	/* Write Face */
	square = 1;
	for (g = 0; g < w->oct.sizeSize; g++) {
		w->oct.facetLoc[view * MAX_SIDES + side][g] = w->oct.faceLoc[g];
		w->oct.facetLoc[view * MAX_SIDES + side][g].rotation =
			(direction == CW) ?
			(w->oct.facetLoc[view * MAX_SIDES + side][g].rotation + 4) % MAX_ORIENT :
			(w->oct.facetLoc[view * MAX_SIDES + side][g].rotation + 8) % MAX_ORIENT;
		drawTriangle(w, view * MAX_SIDES + side, g, FALSE);
		if (g == square * square - 1) {
			++square;
		}
	}
}

static void
readRTT(OctWidget w, int view, int side, int dir, int h, int len, int orient)
{
	int f, g, s;
	int base = h * h;

	if (((dir & 1) == 0) || dir > COORD)
		for (g = 0; g < len; g++)
			w->oct.rowLoc[orient][g] =
				w->oct.facetLoc[view * MAX_SIDES + side][base + g];
	else if (((dir / 2 + side) & 1) == 1) {
		f = -1;
		for (g = 0; g < len; g++) {
			s = g & 1;
			w->oct.rowLoc[orient][g] =
				w->oct.facetLoc[view * MAX_SIDES + side][base + f + 1 - s];
			if (s == SAME)
				f += g + 2 * (h + 1) + 1;
		}
	} else {
		base += 2 * h;
		f = 1;
		for (g = 0; g < len; g++) {
			s = g & 1;
			w->oct.rowLoc[orient][g] =
				w->oct.facetLoc[view * MAX_SIDES + side][base + f - 1 + s];
			if (s == SAME)
				f += g + 2 * h + 1;
		}
	}
}

static void
rotateRTT(OctWidget w, int rotate, int len, int orient)
{
	int g;

	for (g = 0; g < len; g++)
		w->oct.rowLoc[orient][g].rotation =
			(w->oct.rowLoc[orient][g].rotation + rotate) %
			MAX_ORIENT;
}

static void
reverseRTT(OctWidget w, int len, int orient)
{
	int g;
	OctLoc temp;

	for (g = 0; g < (len - 1) / 2; g++) {
		temp = w->oct.rowLoc[orient][len - 1 - g];
		w->oct.rowLoc[orient][len - 1 - g] = w->oct.rowLoc[orient][g];
		w->oct.rowLoc[orient][g] = temp;
	}
}

static void
writeRTT(OctWidget w, int view, int side, int dir, int h, int len, int orient)
{
	int f, g, s;
	int base = h * h;

	if (((dir & 1) == 0) || dir > COORD) {	/* CW || CCW */
		for (g = 0; g < len; g++) {
			s = g & 1;
			w->oct.facetLoc[view * MAX_SIDES + side][base + g] =
				w->oct.rowLoc[orient][g];
			drawTriangle(w, view * MAX_SIDES + side, base + g, FALSE);
		}
	} else if (((dir / 2 + side) & 1) == 1) {
		f = -1;
		for (g = 0; g < len; g++) {
			s = g & 1;
			w->oct.facetLoc[view * MAX_SIDES + side][base + f + 1 - s] =
				w->oct.rowLoc[orient][g];
			drawTriangle(w, view * MAX_SIDES + side,
				base + f + 1 - s, FALSE);
			if (s == SAME)
				f += g + 2 * (h + 1) + 1;
		}
	} else {
		base += 2 * h;
		f = 1;
		for (g = 0; g < len; g++) {
			s = g & 1;
			w->oct.facetLoc[view * MAX_SIDES + side][base + f - 1 + s] =
				w->oct.rowLoc[orient][g];
			drawTriangle(w, view * MAX_SIDES + side,
				base + f - 1 + s, FALSE);
			if (s == SAME)
				f += g + 2 * h + 1;
		}
	}
}

static void
movePieces(OctWidget w, int face, RTT rtt, int direction, int style,
		Boolean control, int fast)
{
	Boolean bound;
	int view, side, orient, newView, rotate, g, h, len;
	int newSide, oldSide, newDirection, l = 0;
	int dir = direction;

#ifdef HAVE_OPENGL
	if (!control && fast != INSTANT && w->oct.dim == 4) {
		movePiecesGL((OctGLWidget) w, face, toPosition(rtt), direction,
			style, control, fast);
	}
#endif
	view = face / MAX_SIDES;
	side = face % MAX_SIDES;
	if (style == PERIOD3) {
		if (dir == CW || dir == CCW) { /* Remap to row movement */
			side = (side + 2) % MAX_SIDES;
			dir = ((side + dir) % MAX_SIDES) * 2;
			/*face = side + MAX_SIDES * ((view == UP) ? DOWN : UP;*/
			rtt.row = rtt.trbl = rtt.tlbr = 0;
		}
		if (((rtt.row == 0) && ((dir & 1) == 0)) ||
				((rtt.trbl == 0) && ((dir & 1) == 1) &&
				(((side + dir / 2) & 1) == 0)) ||
				((rtt.tlbr == 0) && ((dir & 1) == 1) &&
				(((side + dir / 2) & 1) == 1)))
			rotateFace(w, view, rowToRotate[side][dir].face,
				rowToRotate[side][dir].rotation);
		if ((rtt.row == w->oct.size - 1 && ((dir & 1) == 0)) ||
				(rtt.trbl == w->oct.size - 1 &&
				((dir & 1) == 1) &&
				(((side + dir / 2) & 1) == 0)) ||
				(rtt.tlbr == w->oct.size - 1 &&
				((dir & 1) == 1) &&
				(((side + dir / 2) & 1) == 1)))
			rotateFace(w, (view == UP) ? DOWN : UP,
				rowToRotate[MAX_SIDES + side][dir].face,
				rowToRotate[MAX_SIDES + side][dir].rotation);
		if ((dir & 1) == 0)
			h = rtt.row;
		else if (((dir / 2 + side) & 1) == 1)
			h = rtt.tlbr;
		else		/* (((dir / 2 + side) & 1) == 0) */
			h = rtt.trbl;
		if (w->oct.sticky && (h == 1 || h == 2)) {
			l = 0;
			bound = True;
			h = 1;
		} else
			bound = False;
		newView = view;
		newSide = side;
		newDirection = dir;
		do {
			len = length(w, dir, h);
			rotate = rotateOrientP3[side][dir];
			readRTT(w, view, side, dir, h, len, 0);
			if (reverseP3[side][dir] == TRUE)
				reverseRTT(w, len, 0);
			rotateRTT(w, rotate, len, 0);
			for (orient = 1; orient < 8; orient++) {
				if (slideNextRowP3[side][dir].viewChanged) {
					view = (view == UP) ? DOWN : UP;
					h = w->oct.size - 1 - h;
				}
				oldSide = slideNextRowP3[side][dir].face;
				dir = slideNextRowP3[side][dir].direction;
				side = oldSide;
				len = length(w, dir, h);
				rotate = rotateOrientP3[side][dir];
				if (orient < 6) {
					readRTT(w, view, side, dir, h, len, orient);
					if (reverseP3[side][dir] == TRUE)
						reverseRTT(w, len, orient);
					rotateRTT(w, rotate, len, orient);
				}
				if (orient >= 2)
					writeRTT(w, view, side, dir, h, len, orient - 2);
			}
			l++;
			h = 2;
			view = newView;
			side = newSide;
			dir = newDirection;
		} while (bound && l < 2);
	} else {		/* style == PERIOD4 */
		if (dir > TL)
			h = rtt.row;
		else if (((dir / 2 + side) & 1) == 1)
			h = rtt.tlbr;
		else	/* (((dir / 2 + side) & 1) == 0) */
			h = rtt.trbl;

		if (w->oct.sticky &&
				!((dir > TL && h == w->oct.size - 1) ||
				(dir <= TL && h == 0))) {
			l = 0;
			h = (dir <= TL) ? 1 : 0;
			bound = True;
		} else
			bound = False;
		g = 0;
		do {		/* In case this is on an edge */
			len = length(w, dir, h);
			if (g == 1) {
				if (dir > TL) {
					dir = (dir == CW) ? CCW : CW;
					view = (view == UP) ? DOWN : UP;
				} else
					side = (side + 2) % MAX_SIDES;
			}
			readRTT(w, view, side, dir, h, len, 0);
			for (orient = 1; orient <= 4; orient++) {
				if (dir <= TL) {
					if ((side - dir / 2 + COORD) %
							MAX_SIDES < 2) {
						newView = (view == UP) ? DOWN : UP;
						newSide = ((side & 1) == 1) ?
							side : (side + 2) % MAX_SIDES;
						newDirection = (((dir / 2) & 1) == 0) ?
							((dir + 6) % MAX_FACES) : ((dir + 2) % MAX_FACES);
						if ((side & 1) == 1)
							rotate = (((newDirection - dir) / 2 + MAX_SIDES) %
								MAX_SIDES == 1) ? 4 : MAX_ORIENT - 4;
						else
							rotate = (((newDirection - dir) / 2 + MAX_SIDES) %
								MAX_SIDES == 1) ? 2 : MAX_ORIENT - 2;
					} else {	/* Next is on same view */
						newView = view;
						newSide = MAX_SIDES - side - 1;
						if (((dir / 2) & 1) == 1)
							newSide = (newSide + 2) % MAX_SIDES;
						newDirection = dir;
						rotate = ((side - newSide + MAX_SIDES) % MAX_SIDES == 1) ?
							1 : MAX_ORIENT - 1;
					}
				} else {	/* dir == CW || dir == CCW */
					newView = view;
					newSide = (side + dir) % MAX_SIDES;
					newDirection = dir;
					rotate = 3 * newDirection;
				}
				if (orient != 4)
					readRTT(w, newView, newSide, newDirection, h, len, orient);
				rotateRTT(w, rotate, len, orient - 1);
				if (dir <= TL)
					reverseRTT(w, len, orient - 1);
				writeRTT(w, newView, newSide, newDirection, h, len, orient - 1);
				view = newView;
				side = newSide;
				dir = newDirection;
			}
			l++;
			if (w->oct.sticky &&
					!((dir > TL && h == w->oct.size - 1) ||
					(dir <= TL && h == 0)))
				h++;
			else
				g++;
		} while ((bound && l < w->oct.size - 1) ||
				(((dir > TL && h == w->oct.size - 1) ||
				(dir <= TL && h == 0)) && g < 2 && !bound));
	}
#ifdef HAVE_OPENGL
	if (!control && fast == INSTANT && w->oct.dim == 4) {
		drawAllPiecesGL((OctGLWidget) w);
	}
#endif
}

static void
moveControlCb(OctWidget w, int face, int direction, int style, int fast)
{
	int i, j, newFace, newDirection;
	RTT rtt;


#ifdef HAVE_OPENGL
	if (fast != INSTANT && w->oct.dim == 4) {
		movePiecesGL((OctGLWidget) w, face, 0, direction,
			style, True, fast);
	}
#endif
	if (w->oct.sticky && style == PERIOD4) {
		for (i = 0; i < 3; i++) {
			if (i == 2) {
				newFace = (1 - face / MAX_SIDES) *
					MAX_SIDES + (((face & 1) == 1) ?
					(face + MAX_SIDES / 2) % MAX_SIDES :
					face % MAX_SIDES);
				if (direction == CW || direction == CCW) {
					j = i - 2;
					rtt.row = j;
					rtt.trbl = j;
					rtt.tlbr = j;
					newDirection = (direction == CW) ? CCW : CW;
				} else {
					j = i + 1;
					rtt.row = j;
					rtt.trbl = j;
					rtt.tlbr = j;
					newDirection = ((direction / 2) & 1) ?
						(direction + 2) % MAX_FACES :
						(direction + 6) % MAX_FACES;
				}
			} else {
				if (i == 1)
					j = i + 2;
				else
					j = i;
				rtt.row = j;
				rtt.trbl = j;
				rtt.tlbr = j;
				newFace = face;
				newDirection = direction;
			}
			movePieces(w, newFace, rtt, newDirection,
				style, True, fast);
		}
		/*setPuzzleMove(w, ACTION_MOVED, newFace, 0,
			newDirection, style, 1, fast);*/
	} else if (style == PERIOD3) {
		for (i = 0; i < w->oct.size; i++) {
			if (w->oct.sticky && i == 2)
				i++;
			if (direction == CW || direction == CCW) {
				if (face >= MAX_SIDES)
					direction = (direction == CW) ? CCW : CW;
				if (face == 5)
					face = 7;
				else if (face == 7)
					face = 5;
				newFace = (face + 2) % MAX_SIDES;
				newDirection = ((face + direction) % MAX_SIDES) * 2;
				rtt.row = i;
				rtt.trbl = i;
				rtt.tlbr = i;
			} else if (direction % 2 == 0) {
				newFace = face;
				newDirection = direction;
				rtt.row = i;
				rtt.trbl = i;
				rtt.tlbr = i;
			} else if (((direction == TR || direction == BL) &&
					face % 2 == 0) ||
					((direction == TL || direction == BR) &&
					face % 2 == 1)) {
				newFace = face;
				newDirection = direction;
				rtt.row = i;
				rtt.trbl = i;
				rtt.tlbr = 0;
			} else {
				newFace = face;
				newDirection = direction;
				rtt.row = i;
				rtt.trbl = 0;
				rtt.tlbr = i;
			}
			movePieces(w, newFace, rtt, newDirection, style, True, fast);
		}
		/*setPuzzleMove(w, ACTION_MOVED, newFace, 0,
			newDirection, style, 1, fast);*/
	} else	{	/* (style == PERIOD4) */
		for (i = 0; i < 2 * w->oct.size - 1; i++) {
			if (i < w->oct.size) {
				if (direction == CW || direction == CCW  || direction % 2 == 0) {
					rtt.row = i;
					rtt.trbl = i;
					rtt.tlbr = i;
					newFace = face;
					newDirection = direction;
				} else if (((direction == TR || direction == BL) &&
						face % 2 == 0) ||
						((direction == TL || direction == BR) &&
						face % 2 == 1)) {
					newFace = face;
					newDirection = direction;
					rtt.row = i;
					rtt.trbl = i;
					rtt.tlbr = 0;
				} else {
					newFace = face;
					newDirection = direction;
					rtt.row = i;
					rtt.trbl = 0;
					rtt.tlbr = i;
				}
			} else {
				if (direction == CW || direction == CCW  || direction % 2 == 0) {
					newFace = (1 - face / MAX_SIDES) *
						MAX_SIDES + (((face & 1) == 1) ?
						(face + MAX_SIDES / 2) % MAX_SIDES :
						face % MAX_SIDES);
					if (direction == CW || direction == CCW) {
						j = i - w->oct.size;
						rtt.row = j;
						rtt.trbl = j;
						rtt.tlbr = j;
						newDirection = (direction == CW) ? CCW : CW;
					} else {
						j = i - w->oct.size + 1;
						rtt.row = j;
						rtt.trbl = j;
						rtt.tlbr = j;
						newDirection = (((direction / 2) & 1) == 1) ?
							(direction + 2) % MAX_FACES :
							(direction + 6) % MAX_FACES;
					}
				} else if (((direction == TR || direction == BL) &&
						face % 2 == 0) ||
						((direction == TL || direction == BR) &&
						face % 2 == 1)) {
					newFace = ((face / 4) * 4) + ((face % 4) + 2) % 4;
					newDirection = direction;
					j = i - w->oct.size + 1;
					rtt.row = j;
					rtt.trbl = j;
					rtt.tlbr = 0;
				} else {
					newFace = ((face / 4) * 4) + ((face % 4) + 2) % 4;
					newDirection = direction;
					j = i - w->oct.size + 1;
					rtt.row = j;
					rtt.trbl = 0;
					rtt.tlbr = j;
				}
			}
			movePieces(w, newFace, rtt, newDirection,
				style, True, fast);
		}
		/*setPuzzleMove(w, ACTION_MOVED, newFace, 0,
			newDirection, style, 1, fast);*/
	}
#ifdef HAVE_OPENGL
	if (fast == INSTANT && w->oct.dim == 4) {
		drawAllPiecesGL((OctGLWidget) w);
	}
#endif
}

#if 0
static Boolean
moveAllPieces(OctWidget w, const int face, const int position,
		const int direction, const int style, const Boolean control,
		int fast)
{
	int size;
	Boolean newControl;
	RTT rtt;

	size = w->oct.size;
	toRTT(position, &rtt);
	w->oct.degreeTurn = 360 / style;
	if (control || size == 1) {
		/*int k, newFast;*/

		newControl = True;
#ifdef HAVE_OPENGL
		if (newControl && w->oct.dim == 4) {
			movePiecesGL((OctGLWidget) w, face, rtt,
				direction, style, newControl, fast);
		}
#endif
#if 0
			newFast = INSTANT;
		newFast = fast;
		for (k = 0; k < size; k++) {
			newPosition = k * factor;
			toRTT(position, &rtt);
			movePieces(w, face, rtt, direction, style,
				newControl, newFast);
		}
#endif
	} else {
		newControl = False;
		movePieces(w, face, rtt, direction, style, control, fast);
	}
	return newControl;
}
#endif

void
movePuzzle(OctWidget w, int face, int position, int direction,
		int style, int control, int fast)
{
	if (direction == IGNORE_DIR) {
		return;
	}
	if (control) {
		moveControlCb(w, face, direction, style, fast);
		setPuzzleMove(w, ACTION_MOVED, face, 0, direction,
			style, True, fast);
	} else {
		RTT rtt;

		toRTT(position, &rtt);
		movePieces(w, face, rtt, direction, style, False, fast);
		setPuzzleMove(w, ACTION_MOVED, face, position, direction,
			style, False, fast);
	}
#ifdef USE_SOUND
	if (w->oct.sound) {
		playSound(MOVESOUND);
	}
#endif
	setMove(&undo, direction, style, control, face, position);
	flushMoves(w, &redo, FALSE);
}

void
movePuzzleDelay(OctWidget w, int face, int position, int direction,
		int style, int control)
{
	movePuzzle(w, face, position, direction, style, control, NORMAL);
	Sleep((unsigned int) w->oct.delay);
}

static Boolean
selectPieces(OctWidget w, int positionX, int positionY,
		int *face, int *position)
{
	if (w->oct.dim == 2)
		return selectPieces2D((Oct2DWidget) w,
			positionX, positionY, face, position);
#ifdef HAVE_OPENGL
	else if (w->oct.dim == 4)
		return selectPiecesGL((OctGLWidget) w,
			positionX, positionY, face, position);
#endif
	return False;
}

static int
checkMoveDir(OctWidget w, int face1, RTT rtt1, int face2, RTT rtt2,
		int style, int *direction)
{
	int which = -1, count = 0;
	int i, *p1, *p2;

	p1 = &(rtt1.row);
	p2 = &(rtt2.row);
	if (face1 == face2) {
		for (i = 0; i < 3; i++, p1++, p2++)
			if (*p1 == *p2) {
				which = i;
				count++;
			}
		if (count == 1)
			switch (which) {
			case 0:	/* ROW */
				if (rtt2.trbl > rtt1.trbl)
					*direction =
						(2 * face1 + LEFT) % COORD;
				else
					*direction =
						(2 * face1 + RIGHT) % COORD;
				break;
			case 1:	/* TRBL */
				if (rtt2.row > rtt1.row)
					*direction = (2 * face1 + TR) % COORD;
				else
					*direction = (2 * face1 + BL) % COORD;
				break;
			case 2:	/* TLBR */
				if (rtt2.row > rtt1.row)
					*direction = (2 * face1 + TL) % COORD;
				else
					*direction = (2 * face1 + BR) % COORD;
				break;
			default:
				*direction = -1;
				return 0;
			}
		if (!w->oct.vertical && face1 >= MAX_SIDES && *direction > TL)
			*direction = (*direction + MAX_SIDES) % MAX_FACES;
	} else {
		if (style == 3) {
			if ((face1 == 0 && face2 == 1) ||
					(face1 == 2 && face2 == 3) ||
					(face1 == 4 && face2 == 5) ||
					(face1 == 6 && face2 == 7)) {
				if (rtt1.row == rtt2.trbl) {
					which = 0;
					count++;
				}
				if (rtt1.tlbr == rtt2.row) {
					which = 2;
					count++;
				}
			} else if ((face1 == 0 && face2 == 3) ||
					(face1 == 2 && face2 == 1) ||
					(face1 == 4 && face2 == 7) ||
					(face1 == 6 && face2 == 5)) {
				if (rtt1.row == rtt2.tlbr) {
					which = 0;
					count++;
				}
				if (rtt1.trbl == rtt2.row) {
					which = 1;
					count++;
				}
			} else if ((face1 == 0 && face2 == 6) ||
					(face1 == 2 && face2 == 4) ||
					(face1 == 4 && face2 == 2) ||
					(face1 == 6 && face2 == 0)) {
				if (rtt1.trbl == w->oct.size - 1 -
						rtt2.trbl) {
					which = 1;
					count++;
				}
				if (rtt1.tlbr == w->oct.size - 1 -
						rtt2.tlbr) {
					which = 2;
					count++;
				}
			} else if ((face1 == 1 && face2 == 0) ||
					(face1 == 3 && face2 == 2) ||
					(face1 == 5 && face2 == 4) ||
					(face1 == 7 && face2 == 6)) {
				if (rtt1.row == rtt2.tlbr) {
					which = 0;
					count++;
				}
				if (rtt1.trbl == rtt2.row) {
					which = 2;
					count++;
				}
			} else if ((face1 == 1 && face2 == 2) ||
					(face1 == 3 && face2 == 0) ||
					(face1 == 5 && face2 == 6) ||
					(face1 == 7 && face2 == 4)) {
				if (rtt1.row == rtt2.trbl) {
					which = 0;
					count++;
				}
				if (rtt1.tlbr == rtt2.row) {
					which = 1;
					count++;
				}
			} else if ((face1 == 1 && face2 == 5) ||
					(face1 == 3 && face2 == 7) ||
					(face1 == 5 && face2 == 1) ||
					(face1 == 7 && face2 == 3)) {
				if (rtt1.tlbr == w->oct.size - 1 -
						rtt2.tlbr) {
					which = 1;
					count++;
				}
				if (rtt1.trbl == w->oct.size - 1 -
						rtt2.trbl) {
					which = 2;
					count++;
				}
			/* P3 */
			} else if ((face1 == 0 && face2 == 2) ||
					(face1 == 2 && face2 == 0) ||
					(face1 == 4 && face2 == 6) ||
					(face1 == 6 && face2 == 4)) {
				if (rtt1.trbl == rtt2.tlbr) {
					which = 1;
					count++;
				}
				if (rtt1.tlbr == rtt2.trbl) {
					which = 2;
					count++;
				}
			} else if ((face1 == 0 && face2 == 5) ||
					(face1 == 2 && face2 == 7) ||
					(face1 == 4 && face2 == 1) ||
					(face1 == 6 && face2 == 3)) {
				if (rtt1.row == w->oct.size - 1 -
						rtt2.trbl) {
					which = 0;
					count++;
				}
				if (rtt1.trbl == w->oct.size - 1 -
						rtt2.row) {
					which = 1;
					count++;
				}
			} else if ((face1 == 0 && face2 == 7) ||
					(face1 == 2 && face2 == 5) ||
					(face1 == 4 && face2 == 3) ||
					(face1 == 6 && face2 == 1)) {
				if (rtt1.row == w->oct.size - 1 -
						rtt2.tlbr) {
					which = 0;
					count++;
				}
				if (rtt1.tlbr == w->oct.size - 1 -
						rtt2.row) {
					which = 2;
					count++;
				}
			} else if ((face1 == 1 && face2 == 3) ||
					(face1 == 3 && face2 == 1) ||
					(face1 == 5 && face2 == 7) ||
					(face1 == 7 && face2 == 5)) {
				if (rtt1.tlbr == rtt2.trbl) {
					which = 1;
					count++;
				}
				if (rtt1.trbl == rtt2.tlbr) {
					which = 2;
					count++;
				}
			} else if ((face1 == 1 && face2 == 6) ||
					(face1 == 3 && face2 == 4) ||
					(face1 == 5 && face2 == 2) ||
					(face1 == 7 && face2 == 0)) {
				if (rtt1.row == w->oct.size - 1 -
						rtt2.tlbr) {
					which = 0;
					count++;
				}
				if (rtt1.tlbr == w->oct.size - 1 -
						rtt2.row) {
					which = 1;
					count++;
				}
			} else if ((face1 == 1 && face2 == 4) ||
					(face1 == 3 && face2 == 6) ||
					(face1 == 5 && face2 == 0) ||
					(face1 == 7 && face2 == 2)) {
				if (rtt1.row == w->oct.size - 1 -
						rtt2.trbl) {
					which = 0;
					count++;
				}
				if (rtt1.trbl == w->oct.size - 1 -
						rtt2.row) {
					which = 2;
					count++;
				}
			}
		} else /* if (style == 4) */ {
			if ((face1 == 0 && face2 == 1) ||
					(face1 == 2 && face2 == 3) ||
					(face1 == 4 && face2 == 5) ||
					(face1 == 6 && face2 == 7)) {
				if (rtt1.row == rtt2.row) {
					which = 0;
					count++;
				}
				if (rtt1.tlbr == rtt2.trbl) {
					which = 2;
					count++;
				}
			} else if ((face1 == 0 && face2 == 3) ||
					(face1 == 2 && face2 == 1) ||
					(face1 == 4 && face2 == 7) ||
					(face1 == 6 && face2 == 5)) {
				if (rtt1.row == rtt2.row) {
					which = 0;
					count++;
				}
				if (rtt1.trbl == rtt2.tlbr) {
					which = 1;
					count++;
				}
			} else if ((face1 == 0 && face2 == 6) ||
					(face1 == 2 && face2 == 4) ||
					(face1 == 4 && face2 == 2) ||
					(face1 == 6 && face2 == 0)) {
				if (rtt1.trbl == rtt2.tlbr) {
					which = 1;
					count++;
				}
				if (rtt1.tlbr == rtt2.trbl) {
					which = 2;
					count++;
				}
			} else if ((face1 == 1 && face2 == 0) ||
					(face1 == 3 && face2 == 2) ||
					(face1 == 5 && face2 == 4) ||
					(face1 == 7 && face2 == 6)) {
				if (rtt1.row == rtt2.row) {
					which = 0;
					count++;
				}
				if (rtt1.trbl == rtt2.tlbr) {
					which = 2;
					count++;
				}
			} else if ((face1 == 1 && face2 == 2) ||
					(face1 == 3 && face2 == 0) ||
					(face1 == 5 && face2 == 6) ||
					(face1 == 7 && face2 == 4)) {
				if (rtt1.row == rtt2.row) {
					which = 0;
					count++;
				}
				if (rtt1.tlbr == rtt2.trbl) {
					which = 1;
					count++;
				}
			} else if ((face1 == 1 && face2 == 5) ||
					(face1 == 3 && face2 == 7) ||
					(face1 == 5 && face2 == 1) ||
					(face1 == 7 && face2 == 3)) {
				if (rtt1.tlbr == rtt2.trbl) {
					which = 1;
					count++;
				}
				if (rtt1.trbl == rtt2.tlbr) {
					which = 2;
					count++;
				}
			}
		}
		if (count == 1) {
			switch (which) {
			case 0: /* ROW */
				if (directionNextFace[TOP][face1] == face2) {
					*direction = TOP;
				} else if (directionNextFace[RIGHT][face1] == face2) {
					*direction = RIGHT;
				} else if (directionNextFace[BOTTOM][face1] == face2) {
					*direction = BOTTOM;
				} else if (directionNextFace[LEFT][face1] == face2) {
					*direction = LEFT;
				}
				if (style == 3) {
				if (directionNextFaceP3[TOP][face1] == face2) {
					*direction = TOP;
				} else if (directionNextFaceP3[RIGHT][face1] == face2) {
					*direction = RIGHT;
				} else if (directionNextFaceP3[BOTTOM][face1] == face2) {
					*direction = BOTTOM;
				} else if (directionNextFaceP3[LEFT][face1] == face2) {
					*direction = LEFT;
				}
				}
				break;
			case 1: /* TRBL */
				if (directionNextFace[TR][face1] == face2) {
					*direction = TR;
				} else if (directionNextFace[BL][face1] == face2) {
					*direction = BL;
				}
				if (style == 3) {
				if (directionNextFaceP3[TR][face1] == face2) {
					*direction = TR;
				} else if (directionNextFaceP3[BL][face1] == face2) {
					*direction = BL;
				}
				}
				break;
			case 2: /* TLBR */
				if (directionNextFace[TL][face1] == face2) {
					*direction = TL;
				} else if (directionNextFace[BR][face1] == face2) {
					*direction = BR;
				}
				if (style == 3) {
				if (directionNextFaceP3[TL][face1] == face2) {
					*direction = TL;
				} else if (directionNextFaceP3[BR][face1] == face2) {
					*direction = BR;
				}
				}
				break;
			default:
				count = 0;
			}
		}
		if (count >= 3)
			/* Very ambiguous but lets not ignore due to face change */
			count = 2;
	}
	return count;
}

static Boolean
narrowSelection(OctWidget w, int style, int *face, int *position,
		int *direction)
{
	if (w->oct.dim == 2)
		return narrowSelection2D((Oct2DWidget) w, style,
			face, position, direction);
#ifdef HAVE_OPENGL
	else if (w->oct.dim == 4)
		return narrowSelectionGL((OctGLWidget) w, style,
			face, position, direction);
#endif
	return False;
}

static Boolean
positionPieces(OctWidget w, int x, int y, int style,
		int *face, int *position, int *direction)
{
	if (!selectPieces(w, x, y, face, position))
		return False;
#if 0
	if (!w->oct.vertical && *face >= MAX_SIDES &&
			*direction < MAX_FACES)
		*direction = (*direction + MAX_SIDES) % MAX_FACES;
#endif
	return narrowSelection(w, style, face, position, direction);
}

void
movePuzzleInput(OctWidget w, int x, int y, int direction, int shift,
		int control)
{
	int style, face, position;

	if (w->oct.mode == BOTH) {
		style = (shift) ? PERIOD4 : PERIOD3;
	} else {
		if (control && shift)
			style = (w->oct.mode == PERIOD4) ?
				PERIOD3 : PERIOD4;
		else
			style = (w->oct.mode == PERIOD3) ?
				PERIOD3 : PERIOD4;
	}
	if (!w->oct.practice && !control && checkSolved(w)) {
		moveNoPieces(w);
		return;
	}
	if (!positionPieces(w, x, y, style, &face, &position, &direction))
		return;
	control = (control) ? 1 : 0;
	movePuzzle(w, face, position, direction,
		style, control, NORMAL);
	if (!control && checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

void
resetPieces(OctWidget w)
{
	int face, position, orient;

	w->oct.sizeSize = w->oct.size * w->oct.size;
	for (face = 0; face < MAX_FACES; face++) {
		if (w->oct.facetLoc[face])
			free(w->oct.facetLoc[face]);
		if (!(w->oct.facetLoc[face] = (OctLoc *)
				malloc(sizeof (OctLoc) *
					(size_t) w->oct.sizeSize))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		if (startLoc[face])
			free(startLoc[face]);
		if (!(startLoc[face] = (OctLoc *)
				malloc(sizeof (OctLoc) *
					(size_t) w->oct.sizeSize))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	for (orient = 0; orient < MAX_ORIENT / 2; orient++) {
		if (w->oct.rowLoc[orient])
			free(w->oct.rowLoc[orient]);
		if (!(w->oct.rowLoc[orient] = (OctLoc *)
				malloc(sizeof (OctLoc) *
					(size_t) (2 * w->oct.size - 1)))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	if (w->oct.faceLoc)
		free(w->oct.faceLoc);
	if (!(w->oct.faceLoc = (OctLoc *)
			malloc(sizeof (OctLoc) * (size_t) w->oct.sizeSize))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < w->oct.sizeSize; position++) {
			w->oct.facetLoc[face][position].face = face;
			w->oct.facetLoc[face][position].rotation =
				3 * face % MAX_ORIENT;
		}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->oct.started = False;
	w->oct.currentFace = IGNORE_DIR;
	w->oct.currentDirection = IGNORE_DIR;
	w->oct.currentStyle = IGNORE_DIR;
}

static void
readFile(OctWidget w, FILE *fp, char *name)
{
	int c, i, size, mode, sticky, orient, practice, moves;
	char *buf1 = NULL, *buf2 = NULL;

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &size) != 1) {
		(void) printf("corrupt record, expecting an integer for size\n");
		return;
	}
	if (size >= MIN_FACETS) {
		for (i = w->oct.size; i < size; i++) {
			setPuzzle(w, ACTION_INCREMENT);
		}
		for (i = w->oct.size; i > size; i--) {
			setPuzzle(w, ACTION_DECREMENT);
		}
	} else {
		stringCat(&buf1, name, " corrupted: size ");
		intCat(&buf2, buf1, size);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_FACETS);
		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;
	}
	switch (mode) {
	case PERIOD3:
		setPuzzle(w, ACTION_PERIOD3);
		break;
	case PERIOD4:
		setPuzzle(w, ACTION_PERIOD4);
		break;
	case BOTH:
		setPuzzle(w, ACTION_BOTH);
		break;
	default:
		stringCat(&buf1, name, " corrupted: mode ");
		intCat(&buf2, buf1, mode);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, PERIOD3);
		free(buf1);
		stringCat(&buf1, buf2, " and ");
		free(buf2);
		intCat(&buf2, buf1, BOTH);
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &sticky) != 1) {
		(void) printf("corrupt record, expecting an integer for sticky\n");
		return;
	}
	if (w->oct.sticky != (Boolean) sticky) {
		setPuzzle(w, ACTION_STICKY);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &orient) != 1) {
		(void) printf("corrupt record, expecting an integer for orient\n");
		return;
	}
	if (w->oct.orient != (Boolean) orient) {
		setPuzzle(w, ACTION_ORIENTIZE);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &practice) != 1) {
		(void) printf("corrupt record, expecting an integer for practice\n");
		return;
	}
	if (w->oct.practice != (Boolean) practice) {
		setPuzzle(w, ACTION_PRACTICE);
	}
#ifdef WINVER
	resetPieces(w);
#endif
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &moves) != 1) {
		(void) printf("corrupt record, expecting an integer for moves\n");
		return;
	}
	if (!scanStartPosition(fp, w))
		return;
	setPuzzle(w, ACTION_RESTORE);
	setStartPosition(w);
	if (!scanMoves(fp, w, moves))
		return;
	(void) printf("%s: size %d, mode %d, sticky %s, orient %s",
		name, size, mode, BOOL_STRING(sticky), BOOL_STRING(orient));
	(void) printf(", practice %s, moves %d\n",
		BOOL_STRING(practice), moves);
}

static void
getPieces(OctWidget 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->oct.cheat = True; /* Assume the worst. */
}

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

	stringCat(&buf1, CURRENTDELIM, LOGFILE);
	lname = buf1;
	stringCat(&buf1, LOGPATH, FINALDELIM);
	stringCat(&buf2, buf1, LOGFILE);
	free(buf1);
	fname = buf2;
	/* Try current directory first. */
	name = lname;
	if ((fp = fopen(name, "w")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "w")) == NULL) {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	(void) fprintf(fp, "size%c %d\n", SYMBOL, w->oct.size);
	(void) fprintf(fp, "mode%c %d\n", SYMBOL, w->oct.mode);
	(void) fprintf(fp, "sticky%c %d\n", SYMBOL,
		(w->oct.sticky) ? 1 : 0);
	(void) fprintf(fp, "orient%c %d\n", SYMBOL,
		(w->oct.orient) ? 1 : 0);
	(void) fprintf(fp, "practice%c %d\n", SYMBOL,
		(w->oct.practice) ? 1 : 0);
	(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
undoPieces(OctWidget w)
{
	if (madeMoves(&undo) &&
			w->oct.currentFace <= IGNORE_DIR) {
		int face, position, direction, style, control;

		getMove(&undo, &direction, &style, &control,
			&face, &position);
		setMove(&redo, direction, style, control,
			face, position);
		direction = (direction < COORD) ?
			(direction + MAX_SIDES) % MAX_FACES : 3 * COORD - direction;
#if 0
		setPuzzleMove(w, ACTION_MOVED, face, position, direction, style,
			moveAllPieces(w, face, position, direction, style,
			control, DOUBLE), DOUBLE);
		if (!control && checkSolved(w)) {
			setPuzzle(w, ACTION_SOLVED);
		}
#else
		if (control) {
			moveControlCb(w, face, direction, style, DOUBLE);
			setPuzzleMove(w, ACTION_MOVED, face, 0, direction,
				style, True, DOUBLE);
		} else {
			RTT rtt;

			toRTT(position, &rtt);
			movePieces(w, face, rtt, direction, style, False, DOUBLE);
			setPuzzleMove(w, ACTION_UNDO, face, position, direction,
				style, False, DOUBLE);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
#endif
	}
}

static void
redoPieces(OctWidget w)
{
	if (madeMoves(&redo) &&
			w->oct.currentFace <= IGNORE_DIR) {
		int face, position, direction, style, control;

		getMove(&redo, &direction, &style, &control,
			&face, &position);
		setMove(&undo, direction, style, control,
			face, position);
#if 0
		setPuzzleMove(w, ACTION_REDO, face, position, direction, style,
			moveAllPieces(w, face, position, direction, style,
			control, DOUBLE), DOUBLE);
		if (!control && checkSolved(w)) {
			setPuzzle(w, ACTION_SOLVED);
		}
#else
		if (control) {
			moveControlCb(w, face, direction, style, DOUBLE);
			setPuzzleMove(w, ACTION_MOVED, face, 0, direction,
				style, True, DOUBLE);
		} else {
			RTT rtt;

			toRTT(position, &rtt);
			movePieces(w, face, rtt, direction, style, False, DOUBLE);
			setPuzzleMove(w, ACTION_REDO, face, position, direction,
				style, False, DOUBLE);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
#endif
	}
}

void
clearPieces(OctWidget w)
{
	setPuzzle(w, ACTION_CLEAR);
}

static void
practicePieces(OctWidget w)
{
	setPuzzle(w, ACTION_PRACTICE);
}

static void
randomizePieces(OctWidget w)
{
	int randomDirection, face, position, style;
	int big = w->oct.sizeSize * 3 + NRAND(2);

	if (w->oct.currentFace > IGNORE_DIR)
		return;
	w->oct.cheat = False;
	if (w->oct.practice)
		practicePieces(w);
	setPuzzle(w, ACTION_RESET);
	if (big > 1000)
		big = 1000;
	if (w->oct.sticky)
		big /= 3;

#ifdef DEBUG
	big = 3;
#endif

	while (big--) {
		face = NRAND(MAX_FACES);
		if (w->oct.mode == BOTH)
			style = NRAND(MAX_MODES - 1) + PERIOD3;
		else
			style = w->oct.mode;
		if (w->oct.sticky) {
			if (NRAND(2) == 1)
				position = 6; /* center */
			else
				position = (NRAND(2) == 1) ? 0 : 12;
		} else /* (!w->oct.sticky) */
			position = NRAND(w->oct.sizeSize);
		randomDirection = NRAND(MAX_ORIENT / 2);
		if (randomDirection == 4) {
			if (style == PERIOD3) {
				if ((face & 1) == 1)
					randomDirection = BOTTOM;
				else
					randomDirection = RIGHT;
			} else /* style == PERIOD4 */
				randomDirection = CW;
		} else if (randomDirection == 5) {
			if (style == PERIOD3) {
				if ((face & 1) == 1)
					randomDirection = TOP;
				else
					randomDirection = LEFT;
			} else /* style == PERIOD4 */
				randomDirection = CCW;
		} else
			randomDirection = randomDirection * 2 + 1;
		movePuzzle(w, face, position, randomDirection,
			style, FALSE, INSTANT);
		setPuzzle(w, ACTION_MOVED);
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	setPuzzle(w, ACTION_RANDOMIZE);
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
solvePieces(OctWidget w)
{
#ifdef TRY_SOLVE
	solveSomePieces(w);
#else
	if (checkSolved(w) || w->oct.currentFace > IGNORE_DIR)
		return;
	if (((w->oct.size <= 3 || w->oct.sticky) && w->oct.mode == PERIOD3) ||
			(w->oct.size <= 4 && w->oct.mode == PERIOD4))
		solveSomePieces(w);
	else {
		setPuzzle(w, ACTION_SOLVE_MESSAGE);
	}
#endif
}

static void
findPieces(OctWidget w)
{
#ifdef FIND
	if (w->oct.size < 5 && (w->oct.mode == PERIOD4 ||
			w->oct.mode == PERIOD3))
		findSomeMoves(w);
	else {
		setPuzzle(w, ACTION_FIND_MESSAGE);
	}
#endif
}

static void
incrementPieces(OctWidget w)
{
	setPuzzle(w, ACTION_INCREMENT);
}

static Boolean
decrementPieces(OctWidget w)
{
	if (w->oct.size <= MIN_FACETS)
		return False;
	setPuzzle(w, ACTION_DECREMENT);
	return True;
}

static void
orientizePieces(OctWidget w)
{
	setPuzzle(w, ACTION_ORIENTIZE);
}

static void
stickyPieces(OctWidget w)
{
	setPuzzle(w, ACTION_STICKY);
}

static void
viewPieces(OctWidget w)
{
	setPuzzle(w, ACTION_VIEW);
}

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

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

static void
soundPieces(OctWidget w)
{
	w->oct.sound = !w->oct.sound;
	setPuzzle(w, ACTION_SOUND);
}

#ifdef WINVER
static void
setValuesPuzzle(OctWidget w)
{
	struct tagColor {
		int red, green, blue;
	} color;
	char szBuf[80], buf[20], charbuf[2];
	int face;

	w->oct.size = (int) GetPrivateProfileInt(SECTION,
		"size", DEFAULT_FACETS, INIFILE);
	w->oct.mode = (int) GetPrivateProfileInt(SECTION,
		"mode", DEFAULT_MODE, INIFILE);
	w->oct.sticky = (BOOL) GetPrivateProfileInt(SECTION,
		"sticky", DEFAULT_STICKY, INIFILE);
	w->oct.orient = (BOOL) GetPrivateProfileInt(SECTION,
		"orient", DEFAULT_ORIENT, INIFILE);
	w->oct.practice = (BOOL) GetPrivateProfileInt(SECTION,
		"practice", DEFAULT_PRACTICE, INIFILE);
/*#ifdef HAVE_OPENGL
	w->oct.dim = (int) GetPrivateProfileInt(SECTION,
		"dim", 4, INIFILE);
#else
#endif FIXME when GL select working */
	w->oct.dim = (int) GetPrivateProfileInt(SECTION,
		"dim", 2, INIFILE);
	w->oct.view = (int) GetPrivateProfileInt(SECTION,
		"view", 1, INIFILE);
	w->oct.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->oct.reverse = (BOOL) GetPrivateProfileInt(SECTION,
		"reverseVideo", DEFAULT_REVERSE, INIFILE);
	/* cyan */
	(void) GetPrivateProfileString(SECTION,
		"frameColor", "0 255 255", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->oct.frameGC = RGB(color.red, color.green, color.blue);
	/* gray25 */
	(void) GetPrivateProfileString(SECTION,
		"pieceBorder", "64 64 64", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->oct.borderGC = 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->oct.inverseGC = RGB(color.red, color.green, color.blue);
	for (face = 0; face < MAX_FACES; face++) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "faceColor%d", face);
#else
		(void) sprintf(buf, "faceColor%d", face);
#endif
		(void) GetPrivateProfileString(SECTION,
			buf, faceColorString[face],
			szBuf, sizeof (szBuf), INIFILE);
		(void) sscanf(szBuf, "%d %d %d",
			&(color.red), &(color.green), &(color.blue));
		w->oct.faceGC[face] =
			RGB(color.red, color.green, color.blue);
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "faceChar%d", face);
#else
		(void) sprintf(buf, "faceChar%d", face);
#endif
		charbuf[0] = faceColorChar[face];
		charbuf[1] = '\0';
		(void) GetPrivateProfileString(SECTION,
			buf, charbuf, szBuf, sizeof (szBuf), INIFILE);
		w->oct.faceChar[face] = szBuf[0];
	}
	w->oct.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", 10, INIFILE);
	w->oct.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", FALSE, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"moveSound", MOVESOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->oct.moveSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->oct.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->oct.scoreFile, szBuf, 80);
}

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

void
resizePuzzle(OctWidget w)
{
	if (w->oct.dim == 2)
		resizePuzzle2D((Oct2DWidget) w);
#ifdef HAVE_OPENGL
	else if (w->oct.dim == 4)
		resizePuzzleGL((OctGLWidget) w);
#endif
}

void
sizePuzzle(OctWidget w)
{
	resetPieces(w);
	resizePuzzle(w);
}

void
exposePuzzle(OctWidget w)
{
	if (w->oct.dim == 2)
		exposePuzzle2D((Oct2DWidget) w);
#ifdef HAVE_OPENGL
	else if (w->oct.dim == 4)
		exposePuzzleGL((OctGLWidget) w);
#endif
}

#else
static void
getColor(OctWidget w, int face)
{
	XGCValues values;
	XtGCMask valueMask;
	XColor colorCell, rgb;

	valueMask = GCForeground | GCBackground;
	if (w->oct.reverse) {
		values.background = w->oct.foreground;
	} else {
		values.background = w->oct.background;
	}
	if (!w->oct.mono || w->oct.dim == 4) {
		if (XAllocNamedColor(XtDisplay(w),
				DefaultColormapOfScreen(XtScreen(w)),
				w->oct.faceName[face], &colorCell, &rgb)) {
			values.foreground = w->oct.faceColor[face] = colorCell.pixel;
			if (w->oct.faceGC[face])
				XtReleaseGC((Widget) w, w->oct.faceGC[face]);
			w->oct.faceGC[face] = XtGetGC((Widget) w, valueMask, &values);
			return;
		} else {
			char *buf1, *buf2;

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

void
setAllColors(OctWidget w)
{
	XGCValues values;
	XtGCMask valueMask;
	int face;

	valueMask = GCForeground | GCBackground;

	if (w->oct.reverse) {
		values.background = w->oct.background;
		values.foreground = w->oct.foreground;
	} else {
		values.foreground = w->oct.background;
		values.background = w->oct.foreground;
	}
	if (w->oct.inverseGC)
		XtReleaseGC((Widget) w, w->oct.inverseGC);
	w->oct.inverseGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->oct.mono) {
		if (w->oct.reverse) {
			values.background = w->oct.foreground;
			values.foreground = w->oct.background;
		} else {
			values.foreground = w->oct.foreground;
			values.background = w->oct.background;
		}
	} else {
		values.foreground = w->oct.frameColor;
		values.background = w->oct.background;
	}
	if (w->oct.frameGC)
		XtReleaseGC((Widget) w, w->oct.frameGC);
	w->oct.frameGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->oct.mono) {
		if (w->oct.reverse) {
			values.background = w->oct.foreground;
			values.foreground = w->oct.background;
		} else {
			values.foreground = w->oct.foreground;
			values.background = w->oct.background;
		}
	} else {
		values.foreground = w->oct.borderColor;
		values.background = w->oct.background;
	}
	if (w->oct.borderGC)
		XtReleaseGC((Widget) w, w->oct.borderGC);
	w->oct.borderGC = XtGetGC((Widget) w, valueMask, &values);
	for (face = 0; face < MAX_FACES; face++)
		getColor(w, face);
	if (w->oct.fontInfo)
		XSetFont(XtDisplay(w), w->oct.borderGC,
			w->oct.fontInfo->fid);
}

static Boolean
setValuesPuzzle(Widget current, Widget request, Widget renew)
{
	OctWidget c = (OctWidget) current, w = (OctWidget) renew;
	Boolean redraw = False, setColors = False;
	int face;

	checkPieces(w);
	for (face = 0; face < MAX_FACES; face++) {
		if (strcmp(w->oct.faceName[face], c->oct.faceName[face])) {
			setColors = True;
			break;
		}
	}
	if (w->oct.font != c->oct.font ||
			w->oct.borderColor != c->oct.borderColor ||
			w->oct.reverse != c->oct.reverse ||
			w->oct.mono != c->oct.mono) {
		loadFont(w);
		setAllColors(w);
		redraw = True;
	} else if (w->oct.background != c->oct.background ||
			w->oct.foreground != c->oct.foreground ||
			setColors) {
		setAllColors(w);
		redraw = True;
	}
	if (w->oct.orient != c->oct.orient ||
			w->oct.sticky != c->oct.sticky ||
			w->oct.mode != c->oct.mode ||
			w->oct.practice != c->oct.practice) {
		resetPieces(w);
		redraw = True;
	}
	if (w->oct.menu != ACTION_IGNORE) {
		int menu = w->oct.menu;

		w->oct.menu = ACTION_IGNORE;
		switch (menu) {
		case ACTION_GET:
			getPieces(w);
			break;
		case ACTION_WRITE:
			writePieces(w);
			break;
		case ACTION_UNDO:
			undoPieces(w);
			break;
		case ACTION_REDO:
			redoPieces(w);
			break;
		case ACTION_CLEAR:
			clearPieces(w);
			break;
		case ACTION_PRACTICE:
			practicePieces(w);
			break;
		case ACTION_RANDOMIZE:
			randomizePieces(w);
			break;
		case ACTION_SOLVE:
			solvePieces(w);
			break;
		case ACTION_FIND:
			findPieces(w);
			break;
		case ACTION_INCREMENT:
			incrementPieces(w);
			break;
		case ACTION_DECREMENT:
			(void) decrementPieces(w);
			break;
		case ACTION_ORIENTIZE:
			orientizePieces(w);
			break;
		case ACTION_STICKY:
			stickyPieces(w);
			break;
		case ACTION_SPEED:
			speedPieces(w);
			break;
		case ACTION_SLOW:
			slowPieces(w);
			break;
		case ACTION_SOUND:
			soundPieces(w);
			break;
		case ACTION_VIEW:
			viewPieces(w);
			break;
		default:
			break;
		}
	}
	if (w->oct.currentDirection == RESTORE_DIR) {
		setStartPosition(w);
		w->oct.currentDirection = IGNORE_DIR;
		w->oct.currentStyle = IGNORE_DIR;
	} else if (w->oct.currentDirection == CLEAR_DIR) {
		w->oct.currentDirection = IGNORE_DIR;
		w->oct.currentStyle = IGNORE_DIR;
		resetPieces(w);
		redraw = True;
		w->oct.currentDirection = IGNORE_DIR;
	} else if (w->oct.currentDirection > IGNORE_DIR) {
		RTT rtt;
		int face = w->oct.currentFace;
		int position = w->oct.currentPosition;
		int direction = w->oct.currentDirection;
		int style = w->oct.currentStyle;

		w->oct.currentFace = IGNORE_DIR;
		w->oct.currentPosition = IGNORE_DIR;
		w->oct.currentDirection = IGNORE_DIR;
		w->oct.currentStyle = IGNORE_DIR;
		if (w->oct.currentControl) {
			moveControlCb(w, face, direction, style,
				w->oct.currentFast);
		} else {
			toRTT(position, &rtt);
			(void) movePieces(w, face, rtt, direction, style,
				w->oct.currentControl, w->oct.currentFast);
		}
	}
	return (redraw);
}

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

#if defined( USE_SOUND ) && defined( USE_ESOUND )
	if (w->oct.dim == 2)
		(void) shutdown_sound();
#endif
	for (face = 0; face < MAX_FACES; face++)
		XtReleaseGC(old, w->oct.faceGC[face]);
	XtReleaseGC(old, w->oct.borderGC);
	XtReleaseGC(old, w->oct.frameGC);
	XtReleaseGC(old, w->oct.inverseGC);
	XtRemoveCallbacks(old, XtNselectCallback, w->oct.select);
	if (w->oct.fontInfo) {
		XUnloadFont(display, w->oct.fontInfo->fid);
		XFreeFont(display, w->oct.fontInfo);
	}
	deleteMoves(&undo);
	deleteMoves(&redo);
}

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

#ifndef WINVER
static
#endif
void
initializePuzzle(
#ifdef WINVER
OctWidget w, HBRUSH brush
#else
Widget request, Widget renew
#endif
)
{
	int face, orient;
#ifdef WINVER
	setValuesPuzzle(w);
#else
	OctWidget w = (OctWidget) renew;

	w->oct.mono = (DefaultDepthOfScreen(XtScreen(w)) < 2 ||
		w->oct.mono);
	w->oct.fontInfo = NULL;
	for (face = 0; face < MAX_FACES; face++)
		w->oct.faceGC[face] = NULL;
	w->oct.borderGC = NULL;
	w->oct.frameGC = NULL;
	w->oct.inverseGC = NULL;
#endif
	w->oct.focus = False;
	loadFont(w);
	for (face = 0; face < MAX_FACES; face++)
		w->oct.facetLoc[face] = NULL;
	for (orient = 0; orient < MAX_ORIENT / 2; orient++)
		w->oct.rowLoc[orient] = NULL;
	w->oct.faceLoc = NULL;
	checkPieces(w);
	newMoves(&undo);
	newMoves(&redo);
	w->oct.cheat = False;
	resetPieces(w);
#ifdef WINVER
	brush = CreateSolidBrush(w->oct.inverseGC);
	SETBACK(w->core.hWnd, brush);
	(void) SRAND(time(NULL));
#else
	(void) SRAND(getpid());
	setAllColors(w);
#endif
#ifdef USE_SOUND
#ifdef USE_NAS
	dsp = XtDisplay(w);
#endif
#ifdef USE_ESOUND
	if (w->oct.dim == 2)
		(void) init_sound();
#endif
#endif
}

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

void
selectPuzzle(OctWidget w
#ifdef WINVER
, const int x, const int y, const int control
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int control = (int) (event->xkey.state & ControlMask);
#endif

	if (selectPieces(w, x, y, &(w->oct.currentFace),
			&(w->oct.currentPosition))) {
		if (w->oct.currentPosition >= w->oct.sizeSize) {
			w->oct.currentFace = IGNORE_DIR;
			return;
		}
		if (control || w->oct.practice || !checkSolved(w)) {
			drawTriangleAll(w, w->oct.currentFace,
				w->oct.currentPosition, TRUE);
		}
	} else {
		w->oct.currentFace = IGNORE_DIR;
		w->oct.currentDirection = IGNORE_DIR;
#ifdef HAVE_OPENGL
		if (w->oct.dim == 4)
			drawAllPiecesGL((OctGLWidget) w);
#endif
	}
}

void
releasePuzzle(OctWidget w
#ifdef WINVER
, const int x, const int y, const int shift, const int control
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	int style, face, position, count = -1, direction = 0;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int shift = (int) (event->xkey.state & (ShiftMask | LockMask));
	int control = (int) (event->xkey.state & ControlMask);
#endif

	if (w->oct.currentFace <= IGNORE_DIR)
		return;
	drawTriangle(w, w->oct.currentFace, w->oct.currentPosition,
		FALSE);
	if (!control && !w->oct.practice && checkSolved(w))
		moveNoPieces(w);
	else if (selectPieces(w, x, y, &face, &position)) {
		RTT currentRtt, rtt;

		if (w->oct.mode == BOTH) {
			style = (shift) ? PERIOD4 : PERIOD3;
		} else {
			if (control && shift)
				style = (w->oct.mode == PERIOD4) ? PERIOD3 : PERIOD4;
			else
				style = (w->oct.mode == PERIOD3) ? PERIOD3 : PERIOD4;
		}
		toRTT(w->oct.currentPosition, &currentRtt);
		toRTT(position, &rtt);
		count = checkMoveDir(w, w->oct.currentFace, currentRtt,
			face, rtt, style, &direction);
		if (count == 1 && narrowSelection(w, style,
				&(w->oct.currentFace), &position, &direction)) {
			movePuzzle(w, w->oct.currentFace,
				w->oct.currentPosition,
				direction, style, (control) ? 1 : 0, NORMAL);
			if (!control && checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		} else if (count == 2) {
			/* count == 3 too annoying */
			setPuzzle(w, ACTION_AMBIGUOUS);
		} else if (count == 0)
			moveNoPieces(w);
	}
	w->oct.currentFace = IGNORE_DIR;
	w->oct.currentDirection = IGNORE_DIR;
#ifdef HAVE_OPENGL
	if (w->oct.dim == 4 && count != 1) {
		drawAllPiecesGL((OctGLWidget) w);
	}
#endif
}

#ifndef WINVER
void
practicePuzzleWithQuery(OctWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->oct.started)
		practicePieces(w);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	else {
		setPuzzle(w, ACTION_PRACTICE_QUERY);
	}
#endif
}

void
practicePuzzleWithDoubleClick(OctWidget w
, XEvent *event, char **args, int nArgs
)
{
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	if (!w->oct.started)
#endif
		practicePieces(w);
}
#endif

#ifndef WINVER
void
randomizePuzzleWithQuery(OctWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->oct.started)
		randomizePieces(w);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	else {
		setPuzzle(w, ACTION_RANDOMIZE_QUERY);
	}
#endif
}

void
randomizePuzzleWithDoubleClick(OctWidget w
, XEvent *event, char **args, int nArgs
)
{
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	if (!w->oct.started)
#endif
		randomizePieces(w);
}
#endif

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

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

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

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

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

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

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

void
findPuzzle(OctWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	findPieces(w);
}

void
practicePuzzle(OctWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	practicePieces(w);
}

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

#ifdef WINVER
Boolean
#else
void
#endif
decrementPuzzle(OctWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
#ifdef WINVER
	return
#else
	(void)
#endif
	decrementPieces(w);
}

void
orientizePuzzle(OctWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	orientizePieces(w);
}

void
stickyModePuzzle(OctWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	stickyPieces(w);
}

#ifndef WINVER
void
period3ModePuzzle(OctWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_PERIOD3);
}

void
period4ModePuzzle(OctWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_PERIOD4);
}

void
bothModePuzzle(OctWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_BOTH);
}
#endif

void
viewPuzzle(OctWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	viewPieces(w);
}

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

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

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

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

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

#ifdef WINVER
void
periodModePuzzle(OctWidget w, const int mode)
{
	setPuzzle(w, mode + ACTION_PERIOD3);
}

void
dimPuzzle(OctWidget w)
{
	setPuzzle(w, ACTION_DIM);
}

#else

void
movePuzzleCcw(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, CCW,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}

void
movePuzzleCw(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, CW,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}
#endif
