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

/* Methods file for Pyraminx */

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

#ifdef WINVER
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "wpyraminx.ini"
#endif

#define SECTION "setup"

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

static const char faceColorChar[MAX_FACES] =
{'B', 'R', 'Y', 'G'};
#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(PyraminxWidget w);
static void sizePuzzle(PyraminxWidget w);
static Boolean setValuesPuzzle(Widget current, Widget request, Widget renew);
static void destroyPuzzle(Widget old);
static void quitPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void initializePuzzle(Widget request, Widget renew);
static void exposePuzzle(Widget renew, XEvent *event, Region region);
static void hidePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzleWithQuery(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzleWithDoubleClick(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzleWithQuery(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzleWithDoubleClick(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void getPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void writePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void undoPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void redoPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void clearPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void solvePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void incrementPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void decrementPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void orientizePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void stickyModePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void period2ModePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void period3ModePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void bothModePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void speedUpPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void slowDownPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void toggleSoundPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void enterPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void leavePuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTop(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTr(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleCw(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBl(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleCcw(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleInput(PyraminxWidget w,
	int x, int y, int direction, int shift, int control);
static void selectPuzzle(PyraminxWidget w,
	XEvent *event, char **args, int nArgs);
static void releasePuzzle(PyraminxWidget 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>F11: MoveCcw()\n\
 <KeyPress>KP_Divide: MoveCcw()\n\
 <KeyPress>R5: MoveCcw()\n\
 <KeyPress>Up: MoveTop()\n\
 <KeyPress>osfUp: MoveTop()\n\
 <KeyPress>KP_Up: MoveTop()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\n\
 <KeyPress>Prior: MoveTr()\n\
 <KeyPress>KP_9: MoveTr()\n\
 <KeyPress>R9: MoveTr()\n\
 <KeyPress>Left: MoveLeft()\n\
 <KeyPress>osfLeft: MoveLeft()\n\
 <KeyPress>KP_Left: MoveLeft()\n\
 <KeyPress>KP_4: MoveLeft()\n\
 <KeyPress>R10: MoveLeft()\n\
 <KeyPress>F12: MoveCw()\n\
 <KeyPress>Begin: MoveCw()\n\
 <KeyPress>KP_5: MoveCw()\n\
 <KeyPress>R11: MoveCw()\n\
 <KeyPress>Right: MoveRight()\n\
 <KeyPress>osfRight: MoveRight()\n\
 <KeyPress>KP_Right: MoveRight()\n\
 <KeyPress>KP_6: MoveRight()\n\
 <KeyPress>R12: MoveRight()\n\
 <KeyPress>End: MoveBl()\n\
 <KeyPress>KP_1: MoveBl()\n\
 <KeyPress>R13: MoveBl()\n\
 <KeyPress>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\
 <Btn2Down>: PracticeMaybe()\n\
 <Btn2Down>(2+): Practice2()\n\
 <Btn3Down>: RandomizeMaybe()\n\
 <Btn3Down>(2+): Randomize2()\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\
 <KeyPress>p: Practice()\n\
 <KeyPress>i: Increment()\n\
 <KeyPress>d: Decrement()\n\
 <KeyPress>o: Orientize()\n\
 <KeyPress>2: Period2()\n\
 <KeyPress>3: Period3()\n\
 <KeyPress>b: Both()\n\
 <KeyPress>y: Sticky()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsList[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "MoveCcw", (XtActionProc) movePuzzleCcw},
	{(char *) "MoveTop", (XtActionProc) movePuzzleTop},
	{(char *) "MoveTr", (XtActionProc) movePuzzleTr},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveCw", (XtActionProc) movePuzzleCw},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBl", (XtActionProc) movePuzzleBl},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleBottom},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "PracticeMaybe", (XtActionProc) practicePuzzleWithQuery},
	{(char *) "Practice2", (XtActionProc) practicePuzzleWithDoubleClick},
	{(char *) "RandomizeMaybe", (XtActionProc) randomizePuzzleWithQuery},
	{(char *) "Randomize2", (XtActionProc) randomizePuzzleWithDoubleClick},
	{(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 *) "Practice", (XtActionProc) practicePuzzle},
	{(char *) "Increment", (XtActionProc) incrementPuzzle},
	{(char *) "Decrement", (XtActionProc) decrementPuzzle},
	{(char *) "Orientize", (XtActionProc) orientizePuzzle},
	{(char *) "Period2", (XtActionProc) period2ModePuzzle},
	{(char *) "Period3", (XtActionProc) period3ModePuzzle},
	{(char *) "Both", (XtActionProc) bothModePuzzle},
	{(char *) "Sticky", (XtActionProc) stickyModePuzzle},
	{(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(PyraminxWidget, core.width),
	 XtRString, (caddr_t) "200"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(PyraminxWidget, core.height),
	 XtRString, (caddr_t) "400"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(PyraminxWidget, pyraminx.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(PyraminxWidget, pyraminx.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(PyraminxWidget, pyraminx.frameColor),
	 XtRString, (caddr_t) "cyan" /*XtDefaultForeground*/},
	/* Beware color values are swapped */
	{XtNfaceColor0, XtCLabel, XtRString, sizeof (String),
	 XtOffset(PyraminxWidget, pyraminx.faceName[0]),
	 XtRString, (caddr_t) "blue"},
	{XtNfaceColor1, XtCLabel, XtRString, sizeof (String),
	 XtOffset(PyraminxWidget, pyraminx.faceName[1]),
	 XtRString, (caddr_t) "red"},
	{XtNfaceColor2, XtCLabel, XtRString, sizeof (String),
	 XtOffset(PyraminxWidget, pyraminx.faceName[2]),
	 XtRString, (caddr_t) "yellow"},
	{XtNfaceColor3, XtCLabel, XtRString, sizeof (String),
	 XtOffset(PyraminxWidget, pyraminx.faceName[3]),
	 XtRString, (caddr_t) "green"},
	{XtNpieceBorder, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(PyraminxWidget, pyraminx.borderColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultForeground*/},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(PyraminxWidget, pyraminx.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(PyraminxWidget, pyraminx.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(PyraminxWidget, pyraminx.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNsize, XtCSize, XtRInt, sizeof (int),
	 XtOffset(PyraminxWidget, pyraminx.size),
	 XtRString, (caddr_t) "3"}, /*DEFAULTFACETS */
	{XtNsticky, XtCSticky, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.sticky),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULTSTICKY */
	{XtNmode, XtCMode, XtRInt, sizeof (int),
	 XtOffset(PyraminxWidget, pyraminx.mode),
	 XtRString, (caddr_t) "3"}, /*DEFAULT_MODE */
	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.orient),
	 XtRString, (caddr_t) "FALSE"}, /*DEFAULT_ORIENT */
	{XtNpractice, XtCPractice, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.practice),
	 XtRString, (caddr_t) "TRUE"}, /*DEFAULT_PRACTICE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(PyraminxWidget, pyraminx.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(PyraminxWidget, pyraminx.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(PyraminxWidget, pyraminx.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(PyraminxWidget, pyraminx.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(PyraminxWidget, pyraminx.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(PyraminxWidget, pyraminx.select),
	 XtRCallback, (caddr_t) NULL}
};

PyraminxClassRec pyraminxClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Pyraminx",	/* class name */
		sizeof (PyraminxRec),	/* 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 pyraminxWidgetClass = (WidgetClass) & pyraminxClassRec;

void
setPuzzle(PyraminxWidget w, int reason)
{
	pyraminxCallbackStruct cb;

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

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

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

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

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

typedef struct _CRD {
	int column, row, diagonal;
} CRD;

typedef struct _RowNextP3 {
	int viewChanged, face, direction, reverse;
} RowNextP3;
static int directionNextFace[MAX_ORIENT][MAX_FACES] =
{
	{3, 0, 1, 2},
	{3, 3, 1, 1},
	{1, 3, 3, 1},
	{1, 2, 3, 0},
	{2, 2, 0, 0},
	{2, 0, 0, 2}
};
static PyraminxLoc slideNextRowP2[MAX_SIDES][3] =
{
	{
		{2, 0},
		{1, 3},
		{2, 3}
	},
	{
		{2, 0},
		{0, 3},
		{2, 3}
	}
};
static RowNextP3 slideNextRowP3[MAX_SIDES][MAX_ORIENT] =
{
	{
		{TRUE, UP, TR, FALSE},
		{TRUE, UP, TOP, FALSE},
		{FALSE, UP, BOTTOM, TRUE},
		{FALSE, UP, RIGHT, TRUE},
		{TRUE, DOWN, RIGHT, TRUE},
		{TRUE, DOWN, TR, TRUE}
	},
	{
		{FALSE, DOWN, LEFT, TRUE},
		{TRUE, UP, LEFT, TRUE},
		{TRUE, UP, BL, TRUE},
		{TRUE, DOWN, BL, FALSE},
		{TRUE, DOWN, BOTTOM, FALSE},
		{FALSE, DOWN, TOP, TRUE}
	}
};
static int rotOrientRowP3[3][MAX_ORIENT] =
/* current orient, rotation */
{
	{1, 5, 1, 5, 4, 2},
	{2, 0, 2, 0, 5, 3},
	{3, 1, 3, 1, 0, 4}
};

static Point triangleUnit[MAX_SIDES][4] =
{
	{
		{0, 2},
		{1, 0},
		{-1, 1},
		{0, -1}
	},
	{
		{1, 3},
		{-1, 0},
		{1, -1},
		{0, 1}
	}
};
static Point triangleList[MAX_SIDES][4], letterList[MAX_SIDES];
static Point offsetList[MAX_SIDES];

static Pixmap dr = 0; /* dummy for future double buffering */

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

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

	if (w->pyraminx.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, DEFAULTFACETS);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->pyraminx.size = DEFAULTFACETS;
	}
	if (w->pyraminx.mode < PERIOD2 || w->pyraminx.mode > BOTH) {
		intCat(&buf1, "Mode is in error, use 2 for Period2, 3 for Period3, 4 for Both, defaulting to ",
			DEFAULT_MODE);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->pyraminx.mode = DEFAULT_MODE;
	}

}

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

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

#ifdef DEBUG

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

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

static void
printFace(PyraminxWidget w)
{
	int g, h, side;

	for (g = 0; g < w->pyraminx.size; g++) {
		for (h = 0; h < w->pyraminx.size - g; h++)
			for (side = 0; side < MAX_SIDES; side++)
				if (!side || h < w->pyraminx.size - g - 1)
					(void) printf("%d %d  ", w->pyraminx.faceLoc[side][h +
						g * w->pyraminx.size].face,
						w->pyraminx.faceLoc[side][h + g * w->pyraminx.size].rotation);
		(void) printf("\n");
	}
	(void) printf("\n");

#if 0
	int position;
	int square = 1;

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

static void
printRow2(PyraminxWidget w, int orient)
{
	int g, side;

	(void) printf("Row %d:\n", orient);
	for (g = 0; g < w->pyraminx.size; g++)
		for (side = 0; side < MAX_SIDES; side++)
			(void) printf("%d %d  ", w->pyraminx.rowLoc[orient][side][g].face,
				w->pyraminx.rowLoc[orient][side][g].rotation);
	(void) printf("\n");
}

static void
printRow3(PyraminxWidget w, int len, int orient)
{
	int g, side;

	(void) printf("Row %d:\n", orient);
	for (g = 0; g <= len; g++)
		for (side = 0; side < MAX_SIDES; side++)
			if (!side || g < len)
				(void) printf("%d %d  ", w->pyraminx.rowLoc[orient][side][g].face,
				w->pyraminx.rowLoc[orient][side][g].rotation);
	(void) printf("\n");
}

#endif

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

	while (j * j <= i)
		j++;
	return (j - 1);
}
static void
toCRD(PyraminxWidget w, int face, int position, CRD * crd)
{
	int diag, diag2;

	diag = SQRT(position);
	diag2 = diag * diag;
	/* Passing diagonal so there is no sqrt calculation again */
	if ((face & 1) == UP) {
		crd->column = w->pyraminx.size - 1 - (position - diag2) / 2;
		crd->row = w->pyraminx.size - 1 - (diag2 + 2 * diag - position) / 2;
		crd->diagonal = 2 * w->pyraminx.size - 1 - diag;
	} else {
		crd->column = (position - diag2) / 2;
		crd->row = (diag2 + 2 * diag - position) / 2;
		crd->diagonal = diag;
	}
}

static int
toPosition(PyraminxWidget w, CRD crd)
{
	int diag;

	if (crd.diagonal < w->pyraminx.size)
		return (crd.diagonal * crd.diagonal + crd.diagonal +
			crd.column - crd.row);
	diag = 2 * w->pyraminx.size - 1 - crd.diagonal;
	return (diag * diag + diag + crd.row - crd.column);
}

static int
positionCRD(int dir, int i, int j, int side)
{
	if (dir == TOP || dir == BOTTOM)
		return (i);
	else if (dir == RIGHT || dir == LEFT)
		return (j);
	else			/* dir == TR || dir == BL */
		return (i + j + side);
}

static int
length(PyraminxWidget w, int face, int dir, int h)
{
	if (dir == TR || dir == BL)
		return ((face == UP) ? 2 * w->pyraminx.size - 1 - h : h);
	else
		return ((face == UP) ? h : w->pyraminx.size - 1 - h);
}

static void
drawOrientLine(PyraminxWidget w, int orient, int dx, int dy, int side,
		GC borderGC)
{
	int x_1 = 0, x_2 = 0, y_1 = 0, y_2 = 0;
	int temp1 = w->pyraminx.facetLength / 2 + 1;
	int temp2 = w->pyraminx.facetLength + 1;
	int fix = (w->pyraminx.size == 1) ? 1 : 0;

	switch (orient) {
	case TOP:
		x_2 = x_1 = dx + temp1 + 2;
		y_1 = (side == UP) ? dy + temp1 + 2 : dy;
		y_2 = y_1 + w->pyraminx.orientLineLength;
		break;
	case TR:
		x_1 = (side == UP) ? dx + temp2 + 1 : dx + temp1;
		x_2 = x_1 - w->pyraminx.orientDiagLength;
		y_1 = (side == UP) ? dy + temp1 + 1 : dy;
		y_2 = y_1 + w->pyraminx.orientDiagLength;
		break;
	case RIGHT:
		x_1 = (side == UP) ? dx + temp2 + 1 : dx + temp1 - fix - 1;
		x_2 = x_1 - w->pyraminx.orientLineLength;
		y_2 = y_1 = dy + temp1 - 1;
		break;
	case BOTTOM:
		x_2 = x_1 = dx + temp1 - 1;
		y_1 = (side == UP) ? dy + temp2 + 1 : dy + temp1 - fix - 1;
		y_2 = y_1 - w->pyraminx.orientLineLength;
		break;
	case BL:
		x_1 = (side == UP) ? dx + temp1 : dx;
		x_2 = x_1 + w->pyraminx.orientDiagLength;
		y_1 = (side == UP) ? dy + temp2 + 1 : dy + temp1;
		y_2 = y_1 - w->pyraminx.orientDiagLength;
		break;
	case LEFT:
		x_1 = (side == UP) ? dx + temp1 + 2 : dx;
		x_2 = x_1 + w->pyraminx.orientLineLength;
		y_2 = y_1 = dy + temp1 + 2;
		break;
	default:
		{
			char *buf;

			intCat(&buf, "drawOrientLine: orient ",
				orient);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
	DRAWLINE(w, dr, borderGC, x_1, y_1, x_2, y_2);
}

static void
drawTriangle(PyraminxWidget w, int face, int position, int offset)
{
	GC faceGC, borderGC;
	int dx, dy, faceOnView, orient, side, view;
	CRD crd;

	toCRD(w, face, position, &crd);
	view = (face / MAX_SIDES == UP) ? DOWN : UP;
	side = crd.diagonal - crd.column - crd.row;
	if (!w->pyraminx.vertical && view == DOWN) {
		faceOnView = (side == UP) ? DOWN : UP;
		dx = (w->pyraminx.size - crd.column - 1) *
			(w->pyraminx.facetLength + w->pyraminx.delta);
		dy = (w->pyraminx.size - crd.row - 1) *
			(w->pyraminx.facetLength + w->pyraminx.delta);
		orient = (w->pyraminx.facetLoc[face][position].rotation +
			MAX_ORIENT / 2) % MAX_ORIENT;
		if (2 * w->pyraminx.size - crd.column - crd.row - 2 +
				faceOnView >= w->pyraminx.size) {
			dx += w->pyraminx.sideOffset;
			dy += w->pyraminx.sideOffset;
		}
	} else {
		faceOnView = side;
		dx = crd.column * (w->pyraminx.facetLength + w->pyraminx.delta);
		dy = crd.row * (w->pyraminx.facetLength + w->pyraminx.delta);
		orient = w->pyraminx.facetLoc[face][position].rotation;
		if (crd.column + crd.row + faceOnView >= w->pyraminx.size) {
			dx += w->pyraminx.sideOffset;
			dy += w->pyraminx.sideOffset;
		}
	}
	dx += w->pyraminx.puzzleOffset.x + w->pyraminx.delta;
	dy += w->pyraminx.puzzleOffset.y + w->pyraminx.delta;
	if (view == DOWN) {
		if (w->pyraminx.vertical)
			dy += w->pyraminx.viewLength - w->pyraminx.delta - 1;
		else
			dx += w->pyraminx.viewLength - w->pyraminx.delta - 1;
	}
	triangleList[faceOnView][0].x = offsetList[faceOnView].x + dx;
	triangleList[faceOnView][0].y = offsetList[faceOnView].y + dy;
	if (offset) {
		borderGC = w->pyraminx.faceGC[w->pyraminx.facetLoc[face][position].face];
		if (w->pyraminx.mono) {
			faceGC = w->pyraminx.inverseGC;
		} else {
			faceGC = w->pyraminx.borderGC;
		}
	} else {
		faceGC = w->pyraminx.faceGC[w->pyraminx.facetLoc[face][position].face];
		borderGC = w->pyraminx.borderGC;
	}
	POLYGON(w, dr, faceGC, borderGC, triangleList[faceOnView], 3,
		True, False);
	dx += -faceOnView;
	dy += -faceOnView;
	if (w->pyraminx.mono) {
		int letterX, letterY;
		char buf[2];

		buf[0] =
#ifdef WINVER
			w->pyraminx.faceChar[w->pyraminx.facetLoc
				[face][position].face];
#else
			w->pyraminx.faceName[w->pyraminx.facetLoc
				[face][position].face][0];
#endif
		buf[1] = '\0';
		letterX = dx + letterList[faceOnView].x;
		letterY = dy + letterList[faceOnView].y;
		if (offset) {
			borderGC = w->pyraminx.borderGC;
		} else {
			borderGC = w->pyraminx.inverseGC;
		}
		DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, 1);
	}
	if (w->pyraminx.orient)
		drawOrientLine(w, orient, dx, dy, faceOnView, borderGC);
}

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

	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < w->pyraminx.sizeSize; position++)
			drawTriangle(w, face, position, FALSE);
}

static void
eraseFrame(const PyraminxWidget w)
{
	FILLRECTANGLE(w, dr, w->pyraminx.inverseGC,
		0, 0, w->core.width, w->core.height);
}

static void
drawFrame(PyraminxWidget w, Boolean focus)
{
	int startx, starty, lengthx, lengthy, longlength;
	GC gc = (focus) ? w->pyraminx.frameGC : w->pyraminx.borderGC;

	startx = 1 + w->pyraminx.puzzleOffset.x;
	starty = 1 + w->pyraminx.puzzleOffset.y;
	lengthx = w->pyraminx.viewLength - w->pyraminx.delta +
		w->pyraminx.puzzleOffset.x;
	lengthy = w->pyraminx.viewLength - w->pyraminx.delta +
		w->pyraminx.puzzleOffset.y;
	DRAWLINE(w, dr, gc, startx, starty, lengthx, starty);
	DRAWLINE(w, dr, gc, startx, starty, startx, lengthy);
	DRAWLINE(w, dr, gc, startx, lengthy, lengthx, starty);
	if (w->pyraminx.vertical) {
		longlength = 2 * w->pyraminx.viewLength - 2 * w->pyraminx.delta - 1 +
			w->pyraminx.puzzleOffset.y;
		DRAWLINE(w, dr, gc,
			startx, lengthy, startx, longlength);
		DRAWLINE(w, dr, gc,
			lengthx, lengthy, lengthx, longlength);
		DRAWLINE(w, dr, gc,
			startx, longlength, lengthx, longlength);
		DRAWLINE(w, dr, gc,
			startx, longlength, lengthx, lengthy);
		DRAWLINE(w, dr, gc,
			lengthx, starty, lengthx, lengthy);
		DRAWLINE(w, dr, w->pyraminx.frameGC,
			0, lengthy, (int) w->core.width, lengthy);
	} else {
		longlength = 2 * w->pyraminx.viewLength - 2 * w->pyraminx.delta - 1 +
			w->pyraminx.puzzleOffset.x;
		DRAWLINE(w, dr, gc,
			lengthx, starty, longlength, starty);
		DRAWLINE(w, dr, gc,
			lengthx, lengthy, longlength, lengthy);
		DRAWLINE(w, dr, gc,
			longlength, starty, longlength, lengthy);
		DRAWLINE(w, dr, gc,
			longlength, starty, lengthx, lengthy);
		DRAWLINE(w, dr, gc,
			startx, lengthy, lengthx, lengthy);
		DRAWLINE(w, dr, w->pyraminx.frameGC,
			lengthx, 0, lengthx, (int) w->core.height);
	}
}

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

static void
rotateFace(PyraminxWidget w, int view, int faceOnView, int direction)
{
	int g, h, side, face, position;
	CRD crd;

	/* Read Face */
	for (g = 0; g < w->pyraminx.size; g++)
		for (h = 0; h < w->pyraminx.size - g; h++)
			for (side = 0; side < MAX_SIDES; side++)
				if (g + h + side < w->pyraminx.size) {
					if (faceOnView == DOWN) {
						crd.column = h;
						crd.row = g;
						crd.diagonal = h + g + side;
						face = view * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
						position = toPosition(w, crd);
						w->pyraminx.faceLoc[side][h + g * w->pyraminx.size] =
							w->pyraminx.facetLoc[face][position];
					} else {	/* faceOnView == UP */
						crd.column = w->pyraminx.size - 1 - h;
						crd.row = w->pyraminx.size - 1 - g;
						crd.diagonal = crd.column + crd.row + !side;
						face = view * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
						position = toPosition(w, crd);
						w->pyraminx.faceLoc[side][h + g * w->pyraminx.size] =
							w->pyraminx.facetLoc[face][position];
					}
				}
	/* Write Face */
	if (faceOnView == DOWN) {
		for (g = 0; g < w->pyraminx.size; g++)
			for (h = 0; h < w->pyraminx.size - g; h++)
				for (side = 0; side < MAX_SIDES; side++)
					if (g + h + side < w->pyraminx.size) {
						crd.column = h;
						crd.row = g;
						crd.diagonal = h + g + side;
						face = view * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
						position = toPosition(w, crd);
						if (direction == CCW)
							w->pyraminx.facetLoc[face][position] =
								w->pyraminx.faceLoc[side][w->pyraminx.size - 1 - g - h - side +
							h * w->pyraminx.size];
						else	/* direction == CW */
							w->pyraminx.facetLoc[face][position] =
								w->pyraminx.faceLoc[side][g +
											(w->pyraminx.size - 1 - g - h - side) * w->pyraminx.size];
						w->pyraminx.facetLoc[face][position].rotation =
							(direction == CW) ?
							(w->pyraminx.facetLoc[face][position].rotation + 2) %
							MAX_ORIENT :
							(w->pyraminx.facetLoc[face][position].rotation + 4) %
							MAX_ORIENT;
						drawTriangle(w, face, position, FALSE);
					}
	} else {		/* faceOnView == UP */
		for (g = w->pyraminx.size - 1; g >= 0; g--)
			for (h = w->pyraminx.size - 1; h >= w->pyraminx.size - 1 - g; h--)
				for (side = 1; side >= 0; side--)
					if (g + h + side >= w->pyraminx.size) {
						crd.column = h;
						crd.row = g;
						crd.diagonal = h + g + side;
						face = view * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
						position = toPosition(w, crd);
						if (direction == CCW)
							w->pyraminx.facetLoc[face][position] =
								w->pyraminx.faceLoc[!side][g + h - w->pyraminx.size + 1 - !side
											+ (w->pyraminx.size - 1 - h) * w->pyraminx.size];
						else	/* (direction == CW) */
							w->pyraminx.facetLoc[face][position] =
								w->pyraminx.faceLoc[!side][w->pyraminx.size - 1 - g +
											(g + h - w->pyraminx.size + 1 - !side) * w->pyraminx.size];
						w->pyraminx.facetLoc[face][position].rotation =
							(direction == CW) ?
							(w->pyraminx.facetLoc[face][position].rotation + 2) %
							MAX_ORIENT :
							(w->pyraminx.facetLoc[face][position].rotation + 4) %
							MAX_ORIENT;
						drawTriangle(w, face, position, FALSE);
					}
	}
}

static void
readCRD2(PyraminxWidget w, int view, int dir, int h, int orient)
{
	int g, i, j, side, faceOnView, s, face, position, v = view;
	CRD crd;

	if (dir == TOP || dir == BOTTOM)
		for (g = 0; g < w->pyraminx.size; g++)
			for (side = 0; side < MAX_SIDES; side++) {
				crd.column = h;
				crd.row = g;
				crd.diagonal = h + g + side;
				face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
				position = toPosition(w, crd);
				w->pyraminx.rowLoc[orient][side][g] =
					w->pyraminx.facetLoc[face][position];
	} else if (dir == RIGHT || dir == LEFT)
		for (g = 0; g < w->pyraminx.size; g++)
			for (side = 0; side < MAX_SIDES; side++) {
				crd.column = g;
				crd.row = h;
				crd.diagonal = h + g + side;
				face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
				position = toPosition(w, crd);
				w->pyraminx.rowLoc[orient][side][g] =
					w->pyraminx.facetLoc[face][position];
	} else {		/* dir == TR || dir == BL */
		faceOnView = (h < w->pyraminx.size) ? DOWN : UP;
		i = (faceOnView == UP) ? w->pyraminx.size - 1 : 0;
		j = h % w->pyraminx.size;
		for (g = 0; g < w->pyraminx.size; g++) {
			for (side = 0; side < MAX_SIDES; side++) {
				s = (((side == UP) && (faceOnView == UP)) ||
					((side == DOWN) && (faceOnView == DOWN)))
					? DOWN : UP;
				crd.column = i;
				crd.row = j;
				crd.diagonal = i + j + s;
				face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
				position = toPosition(w, crd);
				w->pyraminx.rowLoc[orient][side][g] =
					w->pyraminx.facetLoc[face][position];
				if (side == DOWN) {
					if (faceOnView == UP) {
						if (j == w->pyraminx.size - 1)
							v = (v == UP) ? DOWN : UP;
						j = (j + 1) % w->pyraminx.size;
					} else {	/* faceOnView == DOWN */
						if (!j)
							v = (v == UP) ? DOWN : UP;
						j = (j - 1 + w->pyraminx.size) % w->pyraminx.size;
					}
				}
			}
			i = (faceOnView == UP) ? i - 1 : i + 1;
		}
	}
}

static void
readCRD3(PyraminxWidget w, int view, int faceOnView, int dir, int h, int len, int orient)
{
	int g, i, j, side, s, face, position, faceView = faceOnView;
	CRD crd;

	if (dir == TOP || dir == BOTTOM) {
		for (g = 0; g <= len; g++)
			for (side = 0; side < MAX_SIDES; side++)
				if ((side == DOWN) || g < len) {
					crd.column = h;
					crd.row = (faceView == UP) ? w->pyraminx.size - 1 - g : g;
					crd.diagonal = h + crd.row +
						((((side == UP) && (faceView == DOWN)) ||
						((side == DOWN) && (faceView == UP)))
						? UP : DOWN);
					face = view * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
					position = toPosition(w, crd);
					w->pyraminx.rowLoc[orient][side][g] =
						w->pyraminx.facetLoc[face][position];
				}
	} else if (dir == RIGHT || dir == LEFT) {
		for (g = 0; g <= len; g++)
			for (side = 0; side < MAX_SIDES; side++)
				if ((side == DOWN) || g < len) {
					crd.column = (faceView == UP) ? w->pyraminx.size - 1 - g : g;
					crd.row = h;
					crd.diagonal = h + crd.column +
						((((side == UP) && (faceView == DOWN)) ||
						((side == DOWN) && (faceView == UP)))
						? UP : DOWN);
					face = view * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
					position = toPosition(w, crd);
					w->pyraminx.rowLoc[orient][side][g] =
						w->pyraminx.facetLoc[face][position];
				}
	} else {	/* dir == TR || dir == BL */
		faceView = (h < w->pyraminx.size) ? DOWN : UP;
		i = (faceView == UP) ? w->pyraminx.size - 1 : 0;
		j = h % w->pyraminx.size;
		for (g = 0; g <= len; g++) {
			for (side = 0; side < MAX_SIDES; side++) {
				if (side == DOWN || g < len) {
					s = (((side == UP) && (faceView == UP)) ||
						((side == DOWN) && (faceView == DOWN)))
						? DOWN : UP;
					crd.column = i;
					crd.row = j;
					crd.diagonal = i + j + s;
					face = view * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
					position = toPosition(w, crd);
					w->pyraminx.rowLoc[orient][side][g] =
						w->pyraminx.facetLoc[face][position];
					if (side == DOWN)
						j = (faceView == UP) ? j + 1 : j - 1;
				}
			}
			i = (faceView == UP) ? i - 1 : i + 1;
		}
	}
}

static void
rotateCRD2(PyraminxWidget w, int rotate, int orient)
{
	int g, side;

	for (g = 0; g < w->pyraminx.size; g++)
		for (side = 0; side < MAX_SIDES; side++)
			w->pyraminx.rowLoc[orient][side][g].rotation =
				(w->pyraminx.rowLoc[orient][side][g].rotation + rotate) % MAX_ORIENT;
}

static void
rotateCRD3(PyraminxWidget w, int faceOnView, int dir, int len, int orient)
{
	int g, side, direction, facetOrient;

	for (g = 0; g <= len; g++)
		for (side = 0; side < MAX_SIDES; side++)
			if (side == DOWN || g < len) {
				direction = (faceOnView == UP) ? (dir + 3) % MAX_ORIENT : dir;
				facetOrient = w->pyraminx.rowLoc[orient][side][g].rotation;
				w->pyraminx.rowLoc[orient][side][g].rotation =
					(facetOrient >= 3) ?
					(rotOrientRowP3[facetOrient - 3][direction] + 3) % MAX_ORIENT :
					rotOrientRowP3[facetOrient][direction];
			}
}

static void
reverseCRD2(PyraminxWidget w, int orient)
{
	PyraminxLoc temp;
	int g;

	for (g = 0; g < w->pyraminx.size; g++) {
		temp = w->pyraminx.rowLoc[orient][g & 1][g / 2];
		w->pyraminx.rowLoc[orient][g & 1][g / 2] =
			w->pyraminx.rowLoc[orient][((g & 1) == 1) ? 0 : 1][w->pyraminx.size - 1 - g / 2];
		w->pyraminx.rowLoc[orient][((g & 1) == 1) ? 0 : 1][w->pyraminx.size - 1 - g / 2] =
			temp;
	}
}

static void
reverseCRD3(PyraminxWidget w, int len, int orient)
{
	PyraminxLoc temp;
	int g;

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

static void
writeCRD2(PyraminxWidget w, int view, int dir, int h, int orient)
{
	int g, side, i, j, s, faceOnView, face, position, v = view;
	CRD crd;

	if (dir == TOP || dir == BOTTOM) {
		for (g = 0; g < w->pyraminx.size; g++)
			for (side = 0; side < MAX_SIDES; side++) {
				crd.column = h;
				crd.row = g;
				crd.diagonal = h + g + side;
				face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
				position = toPosition(w, crd);
				w->pyraminx.facetLoc[face][position] =
					w->pyraminx.rowLoc[orient][side][g];
				drawTriangle(w,
					v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN),
					toPosition(w, crd), FALSE);
			}
	} else if (dir == RIGHT || dir == LEFT) {
		for (g = 0; g < w->pyraminx.size; g++)
			for (side = 0; side < MAX_SIDES; side++) {
				crd.column = g;
				crd.row = h;
				crd.diagonal = h + g + side;
				face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
				position = toPosition(w, crd);
				w->pyraminx.facetLoc[face][position] =
					w->pyraminx.rowLoc[orient][side][g];
				drawTriangle(w, face, position, FALSE);
			}
	} else {	/* dir == TR || dir == BL */
		faceOnView = (h < w->pyraminx.size) ? DOWN : UP;
		i = (faceOnView == UP) ? w->pyraminx.size - 1 : 0;
		j = h % w->pyraminx.size;
		for (g = 0; g < w->pyraminx.size; g++) {
			for (side = 0; side < MAX_SIDES; side++) {
				s = (((side == UP) && (faceOnView == UP)) ||
					((side == DOWN) && (faceOnView == DOWN)))
					? DOWN : UP;
				crd.column = i;
				crd.row = j;
				crd.diagonal = i + j + s;
				face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
				position = toPosition(w, crd);
				w->pyraminx.facetLoc[face][position] =
					w->pyraminx.rowLoc[orient][side][g];
				drawTriangle(w, face, position, FALSE);
				if (side == DOWN) {
					if (faceOnView == UP) {
						if (j == w->pyraminx.size - 1) {
							v = (v == UP) ? DOWN : UP;
							j = 0;
						} else
							++j;
					} else {	/* FACE == DOWN */
						if (j == 0) {
							v = (v == UP) ? DOWN : UP;
							j = w->pyraminx.size - 1;
						} else
							--j;
					}
				}
			}
			if (faceOnView == UP)
				--i;
			else	/* faceOnView == DOWN */
				++i;
		}
	}
}

static void
writeCRD3(PyraminxWidget w, int view, int faceOnView, int dir, int h, int len, int orient)
{
	int g, side, i, j, k, s, face, position, v = view, faceView = faceOnView;
	CRD crd;

	if (dir == TOP || dir == BOTTOM) {
		for (k = 0; k <= len; k++)
			for (side = 0; side < MAX_SIDES; side++)
				if ((side == DOWN) || k < len) {
					g = (faceView == UP) ? w->pyraminx.size - 1 - k : k;
					s = (((side == UP) && (faceView == DOWN)) ||
						((side == DOWN) && (faceView == UP)))
						? UP : DOWN;
					crd.column = h;
					crd.row = g;
					crd.diagonal = h + g + s;
					face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
					position = toPosition(w, crd);
					w->pyraminx.facetLoc[face][position] =
						w->pyraminx.rowLoc[orient][side][k];
					drawTriangle(w, face, position, FALSE);
				}
	} else if (dir == RIGHT || dir == LEFT) {
		for (k = 0; k <= len; k++)
			for (side = 0; side < MAX_SIDES; side++)
				if ((side == DOWN) || k < len) {
					g = (faceView == UP) ? w->pyraminx.size - 1 - k : k;
					s = (((side == UP) && (faceView == DOWN)) ||
						((side == DOWN) && (faceView == UP)))
						? UP : DOWN;
					crd.column = g;
					crd.row = h;
					crd.diagonal = h + g + s;
					face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
					position = toPosition(w, crd);
					w->pyraminx.facetLoc[face][position] =
						w->pyraminx.rowLoc[orient][side][k];
					drawTriangle(w, face, position, FALSE);
				}
	} else {		/* dir == TR || dir == BL */
		faceView = (h < w->pyraminx.size) ? DOWN : UP;
		i = (faceView == UP) ? w->pyraminx.size - 1 : 0;
		j = h % w->pyraminx.size;
		for (k = 0; k <= len; k++) {
			for (side = 0; side < MAX_SIDES; side++)
				if ((side == DOWN) || k < len) {
					s = (((side == UP) && (faceView == UP)) ||
						((side == DOWN) && (faceView == DOWN)))
						? DOWN : UP;
					crd.column = i;
					crd.row = j;
					crd.diagonal = i + j + s;
					face = v * MAX_SIDES + ((crd.diagonal >= w->pyraminx.size) ? UP : DOWN);
					position = toPosition(w, crd);
					w->pyraminx.facetLoc[face][position] =
						w->pyraminx.rowLoc[orient][side][k];
					drawTriangle(w, face, position, FALSE);
					if (side == DOWN) {
						if (faceView == UP) {
							if (j == w->pyraminx.size - 1) {
								v = (v == UP) ? DOWN : UP;
								j = 0;
							} else
								++j;
						} else {	/* FACE == DOWN */
							if (j == 0) {
								v = (v == UP) ? DOWN : UP;
								j = w->pyraminx.size - 1;
							} else
								--j;
						}
					}
				}
			if (faceView == UP)
				--i;
			else	/* faceView == DOWN */
				++i;
		}
	}
}

static void
movePieces(PyraminxWidget w, int face, CRD crd, int direction, int style)
{
	Boolean reverse, bound;
	int view, side;
	int newSide, newView, rotate, h, k, newH, faceOnView, len;
	int newFace, newDirection, l = 0;
	int dir = direction;

	view = face / MAX_SIDES;
	side = crd.diagonal - crd.column - crd.row;
	if (style == PERIOD2) {
		/* Period 2 Slide rows */
		h = positionCRD(dir, crd.column, crd.row, side);
		if (w->pyraminx.sticky && (h % 4 == 1 || h % 4 == 2)) {
			bound = True;
			l = 0;
			if (h % 4 == 2)
				h = h - 1;
		} else
			bound = False;
		do {
			readCRD2(w, view, dir, h, 0);
			for (k = 1; k <= 2; k++) {
				rotate = slideNextRowP2[side][dir % 3].rotation;
				if (dir == TR || dir == BL) {
					newView = view;
					newSide = !side;
					newH = 2 * w->pyraminx.size - 1 - h;
					reverse = False;
				} else if (!rotate) {
					newView = !view;
					newSide = side;
					newH = h;
					reverse = False;
				} else {	/* rotate == 3 */
					newView = !view;
					newSide = !side;
					newH = w->pyraminx.size - 1 - h;
					reverse = True;
				}
				if (k != 2)
					readCRD2(w, newView, dir, newH, k);
				rotateCRD2(w, rotate, k - 1);
				if (reverse)
					reverseCRD2(w, k - 1);
				writeCRD2(w, newView, dir, newH, k - 1);
				view = newView;
				h = newH;
				side = newSide;
			}
			l++;
			h++;
		} while (bound && l < 2);
	} else {		/* style == PERIOD3 */
		faceOnView = (crd.diagonal >= w->pyraminx.size) ? UP : DOWN;
		h = positionCRD(dir, crd.column, crd.row, side);
		bound = False;
		if (dir == TR || dir == BL) {
			if (h == w->pyraminx.size)
				rotateFace(w, view, DOWN, (dir == TR) ? CCW : CW);
			else if (h == w->pyraminx.size - 1)
				rotateFace(w, view, UP, (dir == TR) ? CW : CCW);
			else if (w->pyraminx.sticky)
				bound = True;
		} else if (!crd.column && faceOnView == DOWN &&
				(dir == TOP || dir == BOTTOM))
			rotateFace(w, (view == UP) ? DOWN : UP, DOWN,
			 (dir == TOP || dir == LEFT) ? CCW : CW);
		else if (crd.column == w->pyraminx.size - 1 && faceOnView &&
			 (dir == TOP || dir == BOTTOM))
			rotateFace(w, (view == UP) ? DOWN : UP, UP,
				(dir == BOTTOM || dir == RIGHT) ? CCW : CW);
		else if (crd.row == 0 && faceOnView == DOWN &&
			 (dir == RIGHT || dir == LEFT))
			rotateFace(w, (view == UP) ? DOWN : UP, UP,
				(dir == BOTTOM || dir == RIGHT) ? CCW : CW);
		else if (crd.row == w->pyraminx.size - 1 && faceOnView == UP &&
			 (dir == RIGHT || dir == LEFT))
			rotateFace(w, (view == UP) ? DOWN : UP, DOWN,
			 (dir == TOP || dir == LEFT) ? CCW : CW);
		else if (w->pyraminx.sticky)
			bound = True;
		/* Slide rows */
		if (bound) {
			l = 0;
			if (dir == TR || dir == BL)
				h = (faceOnView == UP) ? w->pyraminx.size + 1 : 0;
			else
				h = (faceOnView == UP) ? 0 : 1;
		}
		do {
			len = length(w, faceOnView, dir, h);
			readCRD3(w, view, faceOnView, dir, h, len, 0);
			for (k = 1; k <= 3; k++) {
				newView = (slideNextRowP3[faceOnView][dir].viewChanged)
					? ((view == UP) ? DOWN : UP) : view;
				newFace = slideNextRowP3[faceOnView][dir].face;
				newDirection = slideNextRowP3[faceOnView][dir].direction;
				reverse = slideNextRowP3[faceOnView][dir].reverse;
				newH = w->pyraminx.size - 1 - h;
				if (faceOnView == UP) {
					if (dir == TR || dir == RIGHT)
						newH = 2 * w->pyraminx.size - 1 - h;
					else if (dir == BOTTOM)
						newH = h;
					else if (dir == BL)
						newH = h - w->pyraminx.size;
				} else {
					if (dir == TOP)
						newH = w->pyraminx.size + h;
					else if (dir == TR)
						newH = h;
				}
				if (k != 3)
					readCRD3(w, newView, newFace, newDirection, newH, len, k);
				rotateCRD3(w, faceOnView, dir, len, k - 1);
				if (reverse)
					reverseCRD3(w, len, k - 1);
				writeCRD3(w, newView, newFace, newDirection, newH, len, k - 1);
				view = newView;
				faceOnView = newFace;
				dir = newDirection;
				h = newH;
			}
			h++;
			l++;
		} while (bound && l < w->pyraminx.size - 1);
	}
}

static void
moveControlCb(PyraminxWidget w, int face, int direction, int style)
{
	int i, faceOnView;
	CRD crd;

	faceOnView = face % MAX_SIDES;
	if (w->pyraminx.sticky) {
		if (style == PERIOD2)
			for (i = 0; i < 3; i++) {
				if (direction == TR || direction == BL) {
					crd.column = 0;
					crd.row = 3 * i / 2;
					crd.diagonal = crd.row + i % 2;
					movePieces(w, face, crd, direction, style);
				} else {
					crd.column = 3 * i / 2;
					crd.row = 3 * i / 2;
					crd.diagonal = crd.row + crd.column;
					movePieces(w, face, crd, direction, style);
				}
				setPuzzle(w, ACTION_CONTROL);
		} else		/* (style == PERIOD3) */
			for (i = 0; i < 2; i++) {
				if (direction == TR || direction == BL) {
					crd.column = 1 + faceOnView;
					crd.row = 1 + faceOnView;
					crd.diagonal = crd.row + crd.column + i;
					movePieces(w, face, crd, direction, style);
				} else {
					crd.column = i + 2 * faceOnView;
					crd.row = i + 2 * faceOnView;
					crd.diagonal = crd.row + crd.column + faceOnView;
					movePieces(w, face, crd, direction, style);
				}
				setPuzzle(w, ACTION_CONTROL);
			}
	} else {
		for (i = 0; i < w->pyraminx.size; i++) {
			if (direction == TR || direction == BL) {
				if (style == PERIOD2) {
					crd.column = 0;
					crd.row = i;
					crd.diagonal = crd.row;
					movePieces(w, face, crd, direction, style);
				} else {
					crd.column = faceOnView * (w->pyraminx.size - 1);
					crd.row = i;
					crd.diagonal = crd.column + crd.row + faceOnView;
					movePieces(w, face, crd, direction, style);
				}
			} else {
				crd.column = i;
				crd.row = w->pyraminx.size - 1 - i;
				crd.diagonal = crd.column + crd.row + faceOnView;
				movePieces(w, face, crd, direction, style);
			}
			setPuzzle(w, ACTION_CONTROL);
		}
	}
}

void
movePuzzle(PyraminxWidget w, int face, int position, int direction,
		int style, int control)
{
	if (control)
		moveControlCb(w, face, direction, style);
	else {
		CRD crd;

		toCRD(w, face, position, &crd);
		movePieces(w, face, crd, direction, style);
		setPuzzle(w, ACTION_MOVED);
	}
#ifdef USE_SOUND
	if (w->pyraminx.sound) {
		playSound(MOVESOUND);
	}
#endif
	setMove(&undo, direction, style, control, face, position);
	flushMoves(w, &redo, FALSE);
}

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

static Boolean
selectPieces(PyraminxWidget w, int positionX, int positionY,
		int *face, CRD *crd)
{
	int offset, modI, modJ, side, view;
	int x = positionX - w->pyraminx.puzzleOffset.x;
	int y = positionY - w->pyraminx.puzzleOffset.y;

	if (w->pyraminx.vertical && y > w->pyraminx.viewLength - 1) {
		y -= (w->pyraminx.viewLength - 1);
		view = DOWN;
	} else if (!w->pyraminx.vertical && x > w->pyraminx.viewLength - 1) {
		x -= (w->pyraminx.viewLength - 1);
		view = DOWN;
	} else
		view = UP;
	if (x <= 0 || y <= 0 ||
			x >= w->pyraminx.faceLength + w->pyraminx.delta ||
			y >= w->pyraminx.faceLength + w->pyraminx.delta)
		return False;
	else if (x + y > w->pyraminx.faceLength)
		offset = 2 * w->pyraminx.delta + 1;
	else
		offset = w->pyraminx.delta;
	crd->column = (x - offset) /
		(w->pyraminx.facetLength + w->pyraminx.delta);
	crd->row = (y - offset) /
		(w->pyraminx.facetLength + w->pyraminx.delta);
	modI = (x - offset) % (w->pyraminx.facetLength + w->pyraminx.delta);
	modJ = (y - offset) % (w->pyraminx.facetLength + w->pyraminx.delta);
	side = (modI + modJ > w->pyraminx.facetLength + 1) ? UP : DOWN;
	if (!w->pyraminx.vertical && view == DOWN) {
		crd->row = w->pyraminx.size - crd->row - 1;
		crd->column = w->pyraminx.size - crd->column - 1;
		side = (side == UP) ? DOWN : UP;
	}
	crd->diagonal = crd->row + crd->column + side;
	*face = ((view == UP) ? DOWN : UP) * MAX_SIDES +
		((crd->diagonal >= w->pyraminx.size) ? UP : DOWN);
	return True;
}

static int
checkMoveDir(PyraminxWidget w, int face1, CRD crd1, int face2, CRD crd2,
		int style, int *direction)
{
	int which = -1, count = 0;
	int i, *p1, *p2;

	p1 = &(crd1.column);
	p2 = &(crd2.column);
	if (face1 == face2) {
		for (i = 0; i < 3; i++, p1++, p2++)
			if (*p1 == *p2) {
				which = i;
				count++;
			}
		if (count == 1)
			switch (which) {
			case 0:	/* COLUMN */
				*direction = (crd2.row > crd1.row) ?
					BOTTOM : TOP;
				break;
			case 1:	/* ROW */
				*direction = (crd2.column > crd1.column) ?
					RIGHT : LEFT;
				break;
			case 2:	/* DIAGONAL */
				*direction = (crd2.column > crd1.column) ?
					TR : BL;
				break;
			}
		if (!w->pyraminx.vertical && face1 >= MAX_SIDES &&
				*direction > LEFT)
			*direction = (*direction + MAX_SIDES) % MAX_FACES;
	} else {
		if (crd1.diagonal >= w->pyraminx.size)
			crd1.diagonal = 2 * w->pyraminx.size - 1 -
				crd1.diagonal;
		if (crd2.diagonal >= w->pyraminx.size)
			crd2.diagonal = 2 * w->pyraminx.size - 1 -
				crd2.diagonal;
		if (style == 2) {
			if (crd1.column == crd2.column) {
				which = 0;
				count++;
			}
			if (face1 / 2 == face2 / 2) {
				if (crd1.row == crd2.row) {
					which = 1;
					count++;
				}
				if (crd1.diagonal == crd2.diagonal) {
					which = 2;
					count++;
				}
			} else {
				if (crd1.row == w->pyraminx.size - 1 -
						crd2.row) {
					which = 1;
					count++;
				}
				if (crd1.diagonal == w->pyraminx.size - 1 -
						crd2.diagonal) {
					which = 2;
					count++;
				}
			}
		} else /* if (style == 3) */ {
			if (face1 == 0 && face2 == 1) {
				if (crd1.column == w->pyraminx.size - 1 -
						crd2.row) {
					which = 0;
					count++;
				}
				if (crd1.row == w->pyraminx.size - 1 -
						crd2.column) {
					which = 1;
					count++;
				}
			} else if (face1 == 0 && face2 == 2) {
				if (crd1.row == w->pyraminx.size - 1 -
						crd2.diagonal) {
					which = 1;
					count++;
				}
				if (crd1.diagonal == w->pyraminx.size - 1 -
						crd2.row) {
					which = 2;
					count++;
				}
			} else if (face1 == 0 && face2 == 3) {
				if (crd1.column == w->pyraminx.size - 1 -
						crd2.diagonal) {
					which = 0;
					count++;
				}
				if (crd1.diagonal == crd2.column) {
					which = 2;
					count++;
				}
			} else if (face1 == 1 && face2 == 0) {
				if (crd1.column == w->pyraminx.size - 1 -
						crd2.row) {
					which = 0;
					count++;
				}
				if (crd1.row == w->pyraminx.size - 1 -
						crd2.column) {
					which = 1;
					count++;
				}
			} else if (face1 == 1 && face2 == 2) {
				if (crd1.column == crd2.diagonal) {
					which = 0;
					count++;
				}
				if (crd1.diagonal == w->pyraminx.size - 1 -
						crd2.column) {
					which = 2;
					count++;
				}
			} else if (face1 == 1 && face2 == 3) {
				if (crd1.row == crd2.diagonal) {
					which = 1;
					count++;
				}
				if (crd1.diagonal == crd2.row) {
					which = 2;
					count++;
				}
			} else if (face1 == 2 && face2 == 0) {
				if (crd1.row == w->pyraminx.size - 1 -
						crd2.diagonal) {
					which = 1;
					count++;
				}
				if (crd1.diagonal == w->pyraminx.size - 1 -
						crd2.row) {
					which = 2;
					count++;
				}
			} else if (face1 == 2 && face2 == 1) {
				if (crd1.column == w->pyraminx.size - 1 -
						crd2.diagonal) {
					which = 0;
					count++;
				}
				if (crd1.diagonal == crd2.column) {
					which = 2;
					count++;
				}
			} else if (face1 == 2 && face2 == 3) {
				if (crd1.column == w->pyraminx.size - 1 -
						crd2.row) {
					which = 0;
					count++;
				}
				if (crd1.row == w->pyraminx.size - 1 -
						crd2.column) {
					which = 1;
					count++;
				}
			} else if (face1 == 3 && face2 == 0) {
				if (crd1.column == crd2.diagonal) {
					which = 0;
					count++;
				}
				if (crd1.diagonal == w->pyraminx.size - 1 -
						crd2.column) {
					which = 2;
					count++;
				}
			} else if (face1 == 3 && face2 == 1) {
				if (crd1.row == crd2.diagonal) {
					which = 1;
					count++;
				}
				if (crd1.diagonal == crd2.row) {
					which = 2;
					count++;
				}
			} else if (face1 == 3 && face2 == 2) {
				if (crd1.column == w->pyraminx.size - 1 -
						crd2.row) {
					which = 0;
					count++;
				}
				if (crd1.row == w->pyraminx.size - 1 -
						crd2.column) {
					which = 1;
					count++;
				}
			}
		}
		if (count == 1) {
			switch (which) {
			case 0:	/* COLUMN */
				if (directionNextFace[BOTTOM][face1] == face2) {
					*direction = BOTTOM;
				} else if (directionNextFace[TOP][face1] == face2) {
					*direction = TOP;
				} else {
					*direction = (NRAND(2) == 0) ? BOTTOM :
						TOP;
				}
				break;
			case 1: /* ROW */
				if (directionNextFace[LEFT][face1] == face2) {
					*direction = LEFT;
				} else if (directionNextFace[RIGHT][face1] == face2) {
					*direction = RIGHT;
				} else {
					*direction = (NRAND(2) == 0) ? LEFT :
						RIGHT;
				}
				break;
			case 2: /* DIAGONAL */
				if (directionNextFace[BL][face1] == face2) {
					*direction = BL;
				} else if (directionNextFace[TR][face1] == face2) {
					*direction = TR;
				} else {
					*direction = (NRAND(2) == 0) ? BL : TR;
				}
				break;
			default:
				count = 0;
			}
		}
		if (count >= 3)
			/* Very ambiguous but lets not ignore due to face change */
			count = 2;
	}
	return count;
}

static void
narrowSelection(PyraminxWidget w, int style,
		int *face, CRD * crd, int *direction) {
	if (style == PERIOD2) {
		if (*direction == CW)
			*direction = TR;
		else if (*direction == CCW)
			*direction = BL;
	} else {	/* style == PERIOD3 */
		if (*direction == CW || *direction == CCW) {
			crd->diagonal = w->pyraminx.size -
				((crd->diagonal < w->pyraminx.size) ?
				UP : DOWN);
			*direction = ((*direction == CW &&
				crd->diagonal == w->pyraminx.size) ||
				(*direction == CCW &&
				crd->diagonal != w->pyraminx.size))
				? TR : BL;
			*face = !(*face % 2) + 2 * (*face / 2);
			crd->row = w->pyraminx.size - 1;
			crd->column = 0;
		}
	}
}

static Boolean
positionPieces(PyraminxWidget w, int x, int y, int style,
		int *face, CRD * crd, int *direction)
{
	if (!selectPieces(w, x, y, face, crd))
		return False;
	if (!w->pyraminx.vertical && *face >= MAX_SIDES &&
			*direction < MAX_ORIENT)
		*direction = (*direction + MAX_ORIENT / 2) % MAX_ORIENT;
	narrowSelection(w, style, face, crd, direction);
	return True;
}

#ifndef WINVER
static
#endif
void
movePuzzleInput(PyraminxWidget w, int x, int y, int direction, int shift,
		int control)
{
	int style, face;
	CRD crd;

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

static void
resetPieces(PyraminxWidget w)
{
	int face, position, orient, side;

	w->pyraminx.sizeSize = w->pyraminx.size * w->pyraminx.size;
	for (face = 0; face < MAX_FACES; face++) {
		if (w->pyraminx.facetLoc[face])
			free(w->pyraminx.facetLoc[face]);
		if (!(w->pyraminx.facetLoc[face] = (PyraminxLoc *)
				malloc(sizeof (PyraminxLoc) *
					(size_t) w->pyraminx.sizeSize))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		if (startLoc[face])
			free(startLoc[face]);
		if (!(startLoc[face] = (PyraminxLoc *)
				malloc(sizeof (PyraminxLoc) *
					(size_t) w->pyraminx.sizeSize))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	for (orient = 0; orient < 3; orient++)
		for (side = 0; side < MAX_SIDES; side++) {
			if (w->pyraminx.rowLoc[orient][side])
				free(w->pyraminx.rowLoc[orient][side]);
			if (!(w->pyraminx.rowLoc[orient][side] = (PyraminxLoc *)
					malloc(sizeof (PyraminxLoc) *
						(size_t) w->pyraminx.size))) {
				DISPLAY_ERROR("Not enough memory, exiting.");
			}
		}
	for (side = 0; side < MAX_SIDES; side++) {
		if (w->pyraminx.faceLoc[side])
			free(w->pyraminx.faceLoc[side]);
		if (!(w->pyraminx.faceLoc[side] = (PyraminxLoc *)
				malloc(sizeof (PyraminxLoc) *
					(size_t) w->pyraminx.sizeSize))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < w->pyraminx.sizeSize; position++) {
			w->pyraminx.facetLoc[face][position].face = face;
			w->pyraminx.facetLoc[face][position].rotation = TOP;
		}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->pyraminx.currentFace = IGNORE_DIR;
	w->pyraminx.started = False;
}

static void
resizePieces(PyraminxWidget w)
{
	int i, j;

	w->pyraminx.facetLength = w->pyraminx.faceLength / w->pyraminx.size -
		w->pyraminx.delta - 1;
	for (i = 0; i <= 3; i++)
		for (j = 0; j < MAX_SIDES; j++) {
			triangleList[j][i].x = triangleUnit[j][i].x *
				w->pyraminx.facetLength;
			triangleList[j][i].y = triangleUnit[j][i].y *
				w->pyraminx.facetLength;
		}
	offsetList[DOWN].x = 0;
	offsetList[UP].x = w->pyraminx.facetLength + 1;
	offsetList[DOWN].y = 0;
	offsetList[UP].y = w->pyraminx.facetLength + 1;
	letterList[DOWN].x = w->pyraminx.facetLength / 4 -
		w->pyraminx.letterOffset.x;
	letterList[UP].x = 3 * w->pyraminx.facetLength / 4 + 3 -
		w->pyraminx.letterOffset.x;
	letterList[DOWN].y = w->pyraminx.facetLength / 4 + 1 +
		w->pyraminx.letterOffset.y;
	letterList[UP].y = 3 * w->pyraminx.facetLength / 4 + 1 +
		w->pyraminx.letterOffset.y;
	w->pyraminx.sideOffset = 3 * w->pyraminx.size / 4;
	w->pyraminx.orientLineLength = w->pyraminx.facetLength / 4;
	w->pyraminx.orientDiagLength = MAX(w->pyraminx.orientLineLength - 3, 0);
}

static void
readFile(PyraminxWidget 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->pyraminx.size; i < size; i++) {
			setPuzzle(w, ACTION_INCREMENT);
		}
		for (i = w->pyraminx.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 PERIOD2:
		setPuzzle(w, ACTION_PERIOD2);
		break;
	case PERIOD3:
		setPuzzle(w, ACTION_PERIOD3);
		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, PERIOD2);
		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->pyraminx.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->pyraminx.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->pyraminx.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(PyraminxWidget 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->pyraminx.cheat = True; /* Assume the worst. */
}

static void
writePieces(PyraminxWidget 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->pyraminx.size);
	(void) fprintf(fp, "mode%c %d\n", SYMBOL, w->pyraminx.mode);
	(void) fprintf(fp, "sticky%c %d\n", SYMBOL,
		(w->pyraminx.sticky) ? 1 : 0);
	(void) fprintf(fp, "orient%c %d\n", SYMBOL,
		(w->pyraminx.orient) ? 1 : 0);
	(void) fprintf(fp, "practice%c %d\n", SYMBOL,
		(w->pyraminx.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(PyraminxWidget w)
{
	if (madeMoves(&undo) &&
			w->pyraminx.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 < MAX_ORIENT) ? (direction + MAX_ORIENT / 2) %
			MAX_ORIENT : 3 * MAX_ORIENT - direction;
		if (control)
			moveControlCb(w, face, direction, style);
		else {
			CRD crd;

			toCRD(w, face, position, &crd);
			movePieces(w, face, crd, direction, style);
			setPuzzle(w, ACTION_UNDO);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
}

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

		getMove(&redo, &direction, &style, &control,
			&face, &position);
		setMove(&undo, direction, style, control,
			face, position);
		if (control)
			moveControlCb(w, face, direction, style);
		else {
			CRD crd;

			toCRD(w, face, position, &crd);
			movePieces(w, face, crd, direction, style);
			setPuzzle(w, ACTION_REDO);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
}

static void
clearPieces(PyraminxWidget w)
{
	if (w->pyraminx.currentFace > IGNORE_DIR)
		return;
	resetPieces(w);
	drawAllPieces(w);
	setPuzzle(w, ACTION_RESET);
}

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

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

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

#ifdef DEBUG
	big = 3;
#endif

	while (big--) {
		face = NRAND(MAX_FACES);
		if (w->pyraminx.mode == BOTH)
			style = NRAND(MAX_MODES - 1) + PERIOD2;
		else
			style = w->pyraminx.mode;
		if (w->pyraminx.sticky) {
			if (style == PERIOD2) {
				if (NRAND(3) == 2) {
					position = (NRAND(2)) ? 9 : 6;
					randomDirection = (NRAND(2)) ? TR : BL;
				} else {
					position = (NRAND(2)) ? 6 : 0;
					if (NRAND(2))
						randomDirection = (NRAND(2)) ? LEFT : RIGHT;
					else
						randomDirection = (NRAND(2)) ? TOP : BOTTOM;
				}
			} else {	/* style == PERIOD3 */
				position = 6;
				randomDirection = NRAND(6);
			}
		} else {	/* (!w->pyraminx.sticky) */
			randomDirection = NRAND(MAX_ORIENT);
			position = NRAND(w->pyraminx.sizeSize);
			if (w->pyraminx.mode == BOTH)
				style = NRAND(BOTH);
			else
				style = w->pyraminx.mode;
		}
		movePuzzle(w, face, position, randomDirection, style, FALSE);
		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(PyraminxWidget w)
{
	if (checkSolved(w) || w->pyraminx.currentFace > IGNORE_DIR)
		return;
	if ((w->pyraminx.size == 2 || w->pyraminx.size == 3) &&
			w->pyraminx.mode == PERIOD3)
		solveSomePieces(w);
	else {
		setPuzzle(w, ACTION_SOLVE_MESSAGE);
	}
}

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

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

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

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

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

static void
slowPieces(PyraminxWidget w)
{
	w->pyraminx.delay += 5;
#ifdef HAVE_MOTIF
	setPuzzle(w, ACTION_SPEED);
#endif
}

static void
soundPieces(PyraminxWidget w)
{
	w->pyraminx.sound = !w->pyraminx.sound;
}

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

	w->pyraminx.size = (int) GetPrivateProfileInt(SECTION,
		"size", DEFAULTFACETS, INIFILE);
	w->pyraminx.mode = (int) GetPrivateProfileInt(SECTION,
		"mode", DEFAULT_MODE, INIFILE);
	w->pyraminx.sticky = (BOOL) GetPrivateProfileInt(SECTION,
		"sticky", DEFAULTSTICKY, INIFILE);
	w->pyraminx.orient = (BOOL) GetPrivateProfileInt(SECTION,
		"orient", DEFAULT_ORIENT, INIFILE);
	w->pyraminx.practice = (BOOL) GetPrivateProfileInt(SECTION,
		"practice", DEFAULT_PRACTICE, INIFILE);
	w->pyraminx.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->pyraminx.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->pyraminx.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->pyraminx.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->pyraminx.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->pyraminx.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->pyraminx.faceChar[face] = szBuf[0];
	}
	w->pyraminx.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", 10, INIFILE);
	w->pyraminx.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", FALSE, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"moveSound", MOVESOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->pyraminx.moveSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "Guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->pyraminx.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->pyraminx.scoreFile, szBuf, 80);
}

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

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

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

			stringCat(&buf1, "Color name \"",
				w->pyraminx.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->pyraminx.reverse) {
		values.background = w->pyraminx.foreground;
		values.foreground = w->pyraminx.background;
	} else {
		values.background = w->pyraminx.background;
		values.foreground = w->pyraminx.foreground;
	}
	if (w->pyraminx.faceGC[face])
		XtReleaseGC((Widget) w, w->pyraminx.faceGC[face]);
	w->pyraminx.faceGC[face] = XtGetGC((Widget) w, valueMask, &values);
}

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

	valueMask = GCForeground | GCBackground;

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

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

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

		w->pyraminx.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_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;
		default:
			break;
		}
	}
	return (redraw);
}

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

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

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

#ifndef WINVER
static
#endif
void
resizePuzzle(PyraminxWidget w)
{
	int tempLength;
#ifdef WINVER
	RECT rect;

	/* Determine size of client area */
	(void) GetClientRect(w->core.hWnd, &rect);
	w->core.width = rect.right;
	w->core.height = rect.bottom;
#endif

	w->pyraminx.delta = 4;
	w->pyraminx.vertical = (w->core.height >= w->core.width);
	if (w->pyraminx.vertical)
		tempLength = MIN(w->core.height / 2, w->core.width);
	else
		tempLength = MIN(w->core.height, w->core.width / 2);
	w->pyraminx.facetLength = MAX((tempLength - w->pyraminx.delta + 1) /
		w->pyraminx.size, 0);
	w->pyraminx.faceLength = w->pyraminx.size * w->pyraminx.facetLength;
	w->pyraminx.viewLength = w->pyraminx.faceLength + w->pyraminx.delta + 3;
	if (w->pyraminx.vertical) {
		w->pyraminx.puzzleSize.x = w->pyraminx.viewLength - 1;
		w->pyraminx.puzzleSize.y = 2 * w->pyraminx.viewLength -
			w->pyraminx.delta - 2;
	} else {
		w->pyraminx.puzzleSize.x = 2 * w->pyraminx.viewLength -
			w->pyraminx.delta - 2;
		w->pyraminx.puzzleSize.y = w->pyraminx.viewLength - 1;
	}
	w->pyraminx.puzzleOffset.x = ((int) w->core.width -
		w->pyraminx.puzzleSize.x) / 2;
	w->pyraminx.puzzleOffset.y = ((int) w->core.height -
		w->pyraminx.puzzleSize.y) / 2;
	resizePieces(w);
}

#ifndef WINVER
static
#endif
void
sizePuzzle(PyraminxWidget w)
{
	resetPieces(w);
	resizePuzzle(w);
}

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

	w->pyraminx.mono = (DefaultDepthOfScreen(XtScreen(w)) < 2 ||
		w->pyraminx.mono);
	w->pyraminx.fontInfo = NULL;
	for (face = 0; face < MAX_FACES; face++)
		w->pyraminx.faceGC[face] = NULL;
	w->pyraminx.borderGC = NULL;
	w->pyraminx.frameGC = NULL;
	w->pyraminx.inverseGC = NULL;
#endif
	w->pyraminx.focus = False;
	loadFont(w);
	for (face = 0; face < MAX_FACES; face++)
		w->pyraminx.facetLoc[face] = NULL;
	for (orient = 0; orient < 3; orient++)
		for (side = 0; side < MAX_SIDES; side++)
			w->pyraminx.rowLoc[orient][side] = NULL;
	for (side = 0; side < MAX_SIDES; side++)
		w->pyraminx.faceLoc[side] = NULL;
	checkPieces(w);
	newMoves(&undo);
	newMoves(&redo);
	w->pyraminx.cheat = False;
	sizePuzzle(w);
#ifdef WINVER
	brush = CreateSolidBrush(w->pyraminx.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
	(void) init_sound();
#endif
#endif
}

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

	if (!w->core.visible)
		return;
#endif
	eraseFrame(w);
	drawFrame(w, w->pyraminx.focus);
	drawAllPieces(w);
}

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

#ifndef WINVER
static
#endif
void
selectPuzzle(PyraminxWidget w
#ifdef WINVER
, const int x, const int y, const int control
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	CRD crd;
#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->pyraminx.currentFace), &crd)) {
		w->pyraminx.currentPosition = toPosition(w, crd);
		if (control || w->pyraminx.practice || !checkSolved(w))
			drawTriangle(w, w->pyraminx.currentFace,
				w->pyraminx.currentPosition, TRUE);
	} else
		w->pyraminx.currentFace = IGNORE_DIR;
}

#ifndef WINVER
static
#endif
void
releasePuzzle(PyraminxWidget 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, count = -1, direction = 0;
	CRD crd;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int shift = (int) (event->xbutton.state & (ShiftMask | LockMask));
	int control = (int) (event->xkey.state & ControlMask);
#endif


	if (w->pyraminx.currentFace <= IGNORE_DIR)
		return;
	drawTriangle(w, w->pyraminx.currentFace, w->pyraminx.currentPosition,
		FALSE);
	if (!control && !w->pyraminx.practice && checkSolved(w))
		moveNoPieces(w);
	else if (selectPieces(w, x, y, &face, &crd)) {
		CRD currentCrd;

		if (w->pyraminx.mode == BOTH) {
			style = (shift) ? PERIOD3 : PERIOD2;
		} else {
			if (control && shift)
				style = (w->pyraminx.mode == PERIOD3) ? PERIOD2 : PERIOD3;
			else
				style = (w->pyraminx.mode == PERIOD2) ? PERIOD2 : PERIOD3;
		}
		toCRD(w, w->pyraminx.currentFace, w->pyraminx.currentPosition,
			&currentCrd);
		count = checkMoveDir(w, w->pyraminx.currentFace, currentCrd,
			face, crd, style, &direction);
		if (count == 1) {
			narrowSelection(w, style, &(w->pyraminx.currentFace),
				&crd, &direction);
			movePuzzle(w, w->pyraminx.currentFace,
				w->pyraminx.currentPosition,
				direction, style, (control) ? 1 : 0);
			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->pyraminx.currentFace = IGNORE_DIR;
}

#ifndef WINVER
static void
practicePuzzleWithQuery(PyraminxWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->pyraminx.started)
		practicePieces(w);
#ifdef HAVE_MOTIF
	else {
		setPuzzle(w, ACTION_PRACTICE_QUERY);
	}
#endif
}

static void
practicePuzzleWithDoubleClick(PyraminxWidget w
, XEvent *event, char **args, int nArgs
)
{
#ifdef HAVE_MOTIF
	if (!w->pyraminx.started)
#endif
		practicePieces(w);
}
#endif

#ifndef WINVER
static void
randomizePuzzleWithQuery(PyraminxWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->pyraminx.started)
		randomizePieces(w);
#ifdef HAVE_MOTIF
	else {
		setPuzzle(w, ACTION_RANDOMIZE_QUERY);
	}
#endif
}

static void
randomizePuzzleWithDoubleClick(PyraminxWidget w
, XEvent *event, char **args, int nArgs
)
{
#ifdef HAVE_MOTIF
	if (!w->pyraminx.started)
#endif
		randomizePieces(w);
}
#endif

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

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

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

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

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

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

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

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

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

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

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

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

#ifndef WINVER
static void
period2ModePuzzle(PyraminxWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_PERIOD2);
}

static void
period3ModePuzzle(PyraminxWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_PERIOD3);
}

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

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

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

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

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

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

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

#else

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

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

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

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

static void
movePuzzleCw(PyraminxWidget 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));
}

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

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

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