/*
 * @(#)Dino.c
 *
 * Copyright 1995 - 2023  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 Dino */

#include "file.h"
#include "rngs.h"
#include "sound.h"
#include "DinoP.h"
#include "Dino2dP.h"
#include "Dino3dP.h"
#ifdef HAVE_OPENGL
#include "DinoGLP.h"
#endif

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

#define SECTION "setup"

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

static const char faceColorChar[MAX_FACES] =
{'R', 'B', 'Y', 'G', 'W', 'O'};
#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 Boolean setValuesPuzzle(Widget current, Widget request, Widget renew);
static void destroyPuzzle(Widget old);
static void initializePuzzle(Widget request, Widget renew);

DinoClassRec dinoClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Dino",		/* class name */
		sizeof (DinoRec),	/* 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 dinoWidgetClass = (WidgetClass) & dinoClassRec;

void
setPuzzle(DinoWidget w, int reason)
{
	dinoCallbackStruct cb;

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

static void
setPuzzleMove(DinoWidget w, int reason, int face, int position, int direction,
		int style, int control, int fast)
{
	dinoCallbackStruct 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(DinoWidget w)
{
#ifndef WINVER
	Display *display = XtDisplay(w);
	const char *altfontname = "-*-times-*-r-*-*-*-180-*";
	char buf[512];

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

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

static int facesToDirection[MAX_FACES][MAX_FACES][MAX_ORIENT] =
{
	{
		{-3, -3, -3, -3},
		{2, -1, 3, -3},
		{-1, 2, -3, 1},
		{1, -3, 0, -1},
		{-1, -1, -1, -1},
		{-1, 3, -3, 0}
	},
	{
		{-3, 3, -1, 0},
		{-3, -3, -3, -3},
		{1, -3, 0, -1},
		{-1, -1, -1, -1},
		{-1, 2, -3, 1},
		{2, -1, 3, -3}
	},
	{
		{-3, 3, -1, 0},
		{2, -1, 3, -3},
		{-3, -3, -3, -3},
		{1, -3, 0, -1},
		{-1, 2, -3, 1},
		{-1, -1, -1, -1}
	},
	{
		{-3, 3, -1, 0},
		{-1, -1, -1, -1},
		{2, -1, 3, -3},
		{-3, -3, -3, -3},
		{-1, 2, -3, 1},
		{1, -3, 0, -1}
	},
	{
		{-1, -1, -1, -1},
		{2, -1, 3, -3},
		{-3, 3, -1, 0},
		{1, -3, 0, -1},
		{-3, -3, -3, -3},
		{-1, 2, -3, 1}
	},
	{
		{-1, 2, -3, 1},
		{2, -1, 3, -3},
		{-1, -1, -1, -1},
		{1, -3, 0, -1},
		{-3, 3, -1, 0},
		{-3, -3, -3, -3}
	}
};
static DinoLoc slideCorner[MAX_FACES][MAX_ORIENT][MAX_ORIENT / 2] =
{
	{
		{
			{3, TR, 0},
			{5, BR, 0}
		},
		{
			{3, TL, 1},
			{2, TR, 0}
		},
		{
			{2, TL, 1},
			{1, TR, 0}
		},
		{
			{5, BL, 1},
			{1, TL, 1}
		}
	},
	{
		{
			{2, TL, 0},
			{0, BL, 0}
		},
		{
			{2, BL, 0},
			{4, TL, 0}
		},
		{
			{4, BL, 0},
			{5, TL, 0}
		},
		{
			{0, TL, 0},
			{5, BL, 0}
		}
	},
	{
		{
			{3, TL, 0},
			{0, BR, 0}
		},
		{
			{3, BL, 0},
			{4, TR, 0}
		},
		{
			{4, TL, 1},
			{1, BR, 1}
		},
		{
			{0, BL, 1},
			{1, TR, 1}
		}
	},
	{
		{
			{5, BR, 1},
			{0, TR, 1}
		},
		{
			{5, TR, 1},
			{4, BR, 1}
		},
		{
			{4, TR, 1},
			{2, BR, 1}
		},
		{
			{0, BR, 1},
			{2, TR, 1}
		}
	},
	{
		{
			{3, BL, 1},
			{2, BR, 0}
		},
		{
			{3, BR, 0},
			{5, TR, 0}
		},
		{
			{5, TL, 1},
			{1, BL, 1}
		},
		{
			{2, BL, 1},
			{1, BR, 0}
		}
	},
	{
		{
			{3, BR, 1},
			{4, BR, 0}
		},
		{
			{3, TR, 1},
			{0, TR, 0}
		},
		{
			{0, TL, 1},
			{1, TL, 0}
		},
		{
			{4, BL, 1},
			{1, BL, 0}
		}
	}
};

static int oppFace[MAX_FACES] =
{4, 3, 5, 1, 0, 2};

static DinoCornerLoc oppCorner[MAX_FACES][MAX_ORIENT] =
{
	{
		{4, 3},
		{4, 2},
		{4, 1},
		{4, 0}
	},
	{
		{3, 1},
		{3, 0},
		{3, 3},
		{3, 2}
	},
	{
		{5, 3},
		{5, 2},
		{5, 1},
		{5, 0}
	},
	{
		{1, 1},
		{1, 0},
		{1, 3},
		{1, 2}
	},
	{
		{0, 3},
		{0, 2},
		{0, 1},
		{0, 0}
	},
	{
		{2, 3},
		{2, 2},
		{2, 1},
		{2, 0}
	}
};

static DinoCornerLoc slideNextFace[MAX_FACES][MAX_ORIENT] =
{
	{
		{5, STRT},
		{3, CW},
		{2, STRT},
		{1, CCW}
	},
	{
		{0, CW},
		{2, STRT},
		{4, CCW},
		{5, HALF}
	},
	{
		{0, STRT},
		{3, STRT},
		{4, STRT},
		{1, STRT}
	},
	{
		{0, CCW},
		{5, HALF},
		{4, CW},
		{2, STRT}
	},
	{
		{2, STRT},
		{3, CCW},
		{5, STRT},
		{1, CW}
	},
	{
		{4, STRT},
		{3, HALF},
		{0, STRT},
		{1, HALF}
	}
};

static int faceToRotate2[MAX_FACES][MAX_ORIENT][2] =
{
	{
		{3, 5},
		{2, 3},
		{1, 2},
		{1, 5}
	},
	{
		{0, 2},
		{2, 4},
		{4, 5},
		{0, 5}
	},
	{
		{3, 0},
		{4, 3},
		{1, 4},
		{0, 1}
	},
	{
		{0, 5},
		{4, 5},
		{2, 4},
		{0, 2}
	},
	{
		{2, 3},
		{3, 5},
		{1, 5},
		{1, 2}
	},
	{
		{4, 3},
		{3, 0},
		{0, 1},
		{1, 4}
	}
};

static int faceToRotate[MAX_FACES][MAX_ORIENT] =
{
	{3, 2, 1, 5},
	{2, 4, 5, 0},
	{3, 4, 1, 0},
	{5, 4, 2, 0},
	{3, 5, 1, 2},
	{3, 0, 1, 4}
};

static DinoCornerLoc rotateToRow[MAX_FACES] =
{
	{1, LEFT},
	{0, BOTTOM},
	{0, RIGHT},
	{0, TOP},
	{1, RIGHT},
	{0, LEFT}
};

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

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

	if (w->dino.mode < PERIOD2 || w->dino.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->dino.mode = DEFAULT_MODE;
	}
	if (w->dino.delay < 0) {
		intCat(&buf1, "Delay cannot be negative (",
			w->dino.delay);
		stringCat(&buf2, buf1, "), taking absolute value");
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		w->dino.delay = -w->dino.delay;
	}
}

static int
getStyle(DinoWidget w, int shift, int control, int alt)
{
	if (w->dino.mode == BOTH) {
		if (shift)
			return EDGE;
		else {
			if (alt)
				return MIDDLE;
			else
				return CORNER;
		}
	} else {
		if (control && shift) {
			if (w->dino.mode == PERIOD3)
				return EDGE;
			else if (alt)
				return MIDDLE;
			else
				return CORNER;
		} else if (w->dino.mode == PERIOD2)
			return EDGE;
		else if (alt)
			return MIDDLE;
		else
			return CORNER;
	}
}

Boolean
checkSolved(const DinoWidget w)
{
	int face, position;
	DinoCornerLoc test = {0, FALSE};

	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < MAX_ORIENT; position++) {
			if (!position) {
				test.face = w->dino.cubeLoc[face][position].face;
				test.rotation = w->dino.cubeLoc[face][position].rotation;
			} else if (test.face !=		/*face */
					w->dino.cubeLoc[face][position].face ||
					(w->dino.orient && test.rotation !=	/*STRT - MAX_ORIENT */
					w->dino.cubeLoc[face][position].rotation))
				return False;
		}
	return True;
}

#ifdef DEBUG
void
printCube(const DinoWidget w)
{
	int face, position;

	for (face = 0; face < MAX_FACES; face++) {
		for (position = 0; position < MAX_ORIENT; position++)
			(void) printf("%d %d  ",
				w->dino.cubeLoc[face][position].face,
				w->dino.cubeLoc[face][position].rotation);
		(void) printf("\n");
	}
	(void) printf("\n");
}
#endif

static void
drawTriangle(DinoWidget w, int face, int position, int offset)
{
	if (w->dino.dim == 2)
		drawTriangle2D((Dino2DWidget) w, face, position, offset);
	else if (w->dino.dim == 3)
		drawTriangle3D((Dino3DWidget) w, face, position, offset);
}

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

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

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

static void
drawFrame(const DinoWidget w, const Boolean focus)
{
	if (w->dino.dim == 2)
		drawFrame2D((Dino2DWidget) w, focus);
	else if (w->dino.dim == 3)
		drawFrame3D((Dino3DWidget) w, focus);
/*#ifdef HAVE_OPENGL
	else if (w->dino.dim == 4)
		drawFrameGL((DinoGLWidget) w, focus);
#endif*/
}

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

static void
rotateFace(DinoWidget w, int face, int direction)
{
	int side;

	/* Read Face */
	for (side = 0; side < MAX_ORIENT; side++)
		w->dino.faceLoc[side] = w->dino.cubeLoc[face][side];
	/* Write Face */
	for (side = 0; side < MAX_ORIENT; side++) {
		w->dino.cubeLoc[face][side] = (direction == CW) ?
			w->dino.faceLoc[(side + MAX_ORIENT - 1) % MAX_ORIENT] :
			w->dino.faceLoc[(side + 1) % MAX_ORIENT];
		w->dino.cubeLoc[face][side].rotation =
			(w->dino.cubeLoc[face][side].rotation + direction) %
			MAX_ORIENT;
		drawTriangle(w, face, side, FALSE);
	}
}

static void
readFace(DinoWidget w, int face, int h)
{
	int side;

	for (side = 0; side < MAX_ORIENT; side++)
		w->dino.rowLoc[h][side] = w->dino.cubeLoc[face][side];
}

static void
writeFace(DinoWidget w, int face, int rotate, int h)
{
	int side, newSide;

	for (side = 0; side < MAX_ORIENT; side++) {
		newSide = (side + rotate) % MAX_ORIENT;
		w->dino.cubeLoc[face][newSide] = w->dino.rowLoc[h][side];
		w->dino.cubeLoc[face][newSide].rotation =
			(w->dino.cubeLoc[face][newSide].rotation + rotate) %
			MAX_ORIENT;
		drawTriangle(w, face, (side + rotate) % MAX_ORIENT, FALSE);
	}
}

static void
readDiagonal(DinoWidget w, int face, int corner, int h)
{
	w->dino.spindleLoc[h][0] = w->dino.cubeLoc[face][corner];
	w->dino.spindleLoc[h][1] = w->dino.cubeLoc[face][(corner + 1) % MAX_ORIENT];
}

static void
writeDiagonal(DinoWidget w, int face, int corner, int rotate, int h)
{
	w->dino.spindleLoc[h][0].rotation =
		(w->dino.spindleLoc[h][0].rotation + rotate) % MAX_ORIENT;
	w->dino.spindleLoc[h][1].rotation =
		(w->dino.spindleLoc[h][1].rotation + rotate) % MAX_ORIENT;
	w->dino.cubeLoc[face][corner] = w->dino.spindleLoc[h][0];
	drawTriangle(w, face, corner, FALSE);
	w->dino.cubeLoc[face][(corner + 1) % MAX_ORIENT] = w->dino.spindleLoc[h][1];
	drawTriangle(w, face, (corner + 1) % MAX_ORIENT, FALSE);
}

static void
moveEdges(DinoWidget w, int face, int corner, int direction)
{
	int oldFace = face, corn = corner, dir = direction;
	int k, newFace, rotate, newCorner, newDirection;

	readDiagonal((DinoWidget) w, oldFace, corn, 0);
	for (k = 1; k <= 2; k++) {
		newFace = oppFace[oldFace];
		/*rotate = (((oldFace == 1 || oldFace == 3) ? 1 : 3) + 3 * dir) %
		MAX_ORIENT; */
		newCorner = ((((((oldFace == 1 || oldFace == 3) ? 1 : 0) +
			corn) & 1) == 1) ? (corn + 3) :
			(corn + 1)) % MAX_ORIENT;
		rotate = (newCorner - corn + MAX_ORIENT) % MAX_ORIENT;
		newDirection = (rotate + dir) % MAX_ORIENT;
		if (k != 2)
			readDiagonal((DinoWidget) w, newFace, newCorner, k);
		writeDiagonal(w, newFace, newCorner, rotate, k - 1);
		oldFace = newFace;
		corn = newCorner;
		dir = newDirection;
	}
}

static void
moveFaces(DinoWidget w, int f, int d, int rotate)
{
	int k, face, newFace, rot = rotate;

	face = faceToRotate2[f][d][0];
	readFace((DinoWidget) w, face, 0);
	for (k = 1; k <= 2; k++) {
		newFace = faceToRotate2[f][d][k & 1];
		rot = MAX_ORIENT - rot;
		if (k != 2)
			readFace((DinoWidget) w, newFace, k);
		writeFace(w, newFace, rot, k - 1);
		face = newFace;
	}
}

static void
moveInsideCorners(DinoWidget w, int face, int corner, int direction)
{
	int oldFace = face, corn = corner, oldDir = direction;
	int newFace, newCorner, newDirection, dir, k;

	readDiagonal((DinoWidget) w, oldFace, corn, 0);
	for (k = 1; k <= MAX_ROTATE; k++) {
		dir = oldDir / 2;
		newFace = slideCorner[oldFace][corn][dir].face;
		newCorner = slideCorner[oldFace][corn][dir].side;
		newDirection = 2 * slideCorner[oldFace][corn][dir].dir +
			(((newCorner & 1) == 0) ? 1 : 0);
		if (k != MAX_ROTATE)
			readDiagonal((DinoWidget) w, newFace, newCorner, k);
		writeDiagonal(w, newFace, newCorner,
			(newDirection - oldDir + MAX_ORIENT) %
			MAX_ORIENT, k - 1);
		oldFace = newFace;
		corn = newCorner;
		oldDir = newDirection;
	}
}

static void
moveOutsideCorners(DinoWidget w, int face, int corner, int direction)
{
	int oldFace = face, corn = corner, oldDir = direction;
	int newFace, newCorner, newDirection, dir, k;

	readDiagonal((DinoWidget) w, oldFace, corn, 0);
	for (k = 1; k <= MAX_ROTATE; k++) {
		corn = (corn + 2) % MAX_ORIENT;
		dir = oldDir / 2;
		newFace = slideCorner[oldFace][corn][dir].face;
		newCorner = (slideCorner[oldFace][corn][dir].side + 2) %
			MAX_ORIENT;
		newDirection = 2 * slideCorner[oldFace][corn][dir].dir +
			(((newCorner & 1) == 0) ? 1 : 0);
		if (k != MAX_ROTATE)
			readDiagonal((DinoWidget) w, newFace, newCorner, k);
		writeDiagonal(w, newFace, newCorner,
			(newDirection - oldDir + MAX_ORIENT) %
			MAX_ORIENT, k - 1);
		oldFace = newFace;
		corn = newCorner;
		oldDir = newDirection;
	}
}

static void
movePieces(DinoWidget w, int face, int position, int direction, int style,
		int control, int fast)
{
	int oldFace = face, dir = direction;
	int corner, newCorner;

	corner = (position - ((((position + dir) & 1) == 0) ?
		1 : 0) + MAX_ORIENT) % MAX_ORIENT;
#ifdef HAVE_OPENGL
	if (control != 2 && fast != INSTANT && w->dino.dim == 4) {
		movePiecesGL((DinoGLWidget) w, oldFace, position, dir, style,
			(control == 1), fast);
	}
#endif
	if (style == CORNER) {
		moveInsideCorners(w, oldFace, corner, dir);
	} else if (style == MIDDLE) {
		moveOutsideCorners(w, oldFace, corner, dir);
		newCorner = oppCorner[oldFace][corner].rotation;
		oldFace = oppCorner[oldFace][corner].face;
		if (((((oldFace == 1 || oldFace == 3) ? 0 : 1) + corner) &
				1) == 1)
			dir = (dir + 1) % MAX_ORIENT;
		else
			dir = (dir + 3) % MAX_ORIENT;
		corner = newCorner;
		moveOutsideCorners(w, oldFace, corner, dir);
	} else if (style == EDGE) {
		moveEdges(w, oldFace, corner, dir);
		if (oldFace == 0 && corner == 1 && (dir & 1) == 1)
			moveFaces(w, oldFace, corner, 0);
		else if (oldFace == 0 && corner == 3 && (dir & 1) == 0)
			moveFaces(w, oldFace, corner, 0);
		else if (oldFace == 0 && corner == 0)
			moveFaces(w, oldFace, corner, 0);
		else if (oldFace == 4 && corner == 1 && (dir & 1) == 0)
			moveFaces(w, oldFace, corner, 0);
		else if (oldFace == 4 && corner == 3 && (dir & 1) == 1)
			moveFaces(w, oldFace, corner, 0);
		else if (oldFace == 4 && corner == 2)
			moveFaces(w, oldFace, corner, 0);
		else
			moveFaces(w, oldFace, corner,
				((oldFace == 2 || oldFace == 5) ? CCW : HALF) %
				MAX_ORIENT);
	} else {
		int k, newFace, rotate, newDirection;
		rotateFace(w, faceToRotate[oldFace][dir % MAX_ORIENT], CW);
		rotateFace(w, faceToRotate[oldFace][(dir + 2) % MAX_ORIENT],
			CCW);
		readFace((DinoWidget) w, oldFace, 0);
		for (k = 1; k <= MAX_ORIENT; k++) {
			newFace = slideNextFace[oldFace][dir % MAX_ORIENT].face;
			rotate = slideNextFace[oldFace][dir % MAX_ORIENT].rotation;
			newDirection = (rotate + dir) % MAX_ORIENT;
			if (k != MAX_ORIENT)
				readFace((DinoWidget) w, newFace, k);
			writeFace(w, newFace, rotate, k - 1);
			oldFace = newFace;
			dir = newDirection;
		}
	}
#ifdef HAVE_OPENGL
	if (!control && fast == INSTANT && w->dino.dim == 4) {
		drawAllPiecesGL((DinoGLWidget) w);
	}
#endif
#ifdef DEBUG
	printCube(w);
#endif
}

static void
moveControlCb(DinoWidget w, int face, int position, int direction, int style,
		int fast)
{
	int newFace, newSide, newDirection, corner, newCorner;

	movePieces(w, face, position, direction, style, 1, fast);
	setPuzzleMove(w, ACTION_CONTROL, face, position, direction, style, 1, fast);
	if (style == CORNER) {
		newSide = (position + 2) % MAX_ORIENT;
		movePieces(w, face, newSide, direction, MIDDLE, 2, fast);
		setPuzzleMove(w, ACTION_CONTROL, face, newSide, direction,
			MIDDLE, 2, fast);
		corner = (position - ((((position + direction) & 1) ==
			0) ? 1 : 0) + MAX_ORIENT) % MAX_ORIENT;
		newFace = oppCorner[face][corner].face;
		newCorner = oppCorner[face][corner].rotation;
		newDirection = 2 * ((direction / 2 +
			((face == 1 || face == 3) ? 0 : 1)) & 1) +
			(((newCorner & 1) == 0) ? 1 : 0);
		newSide = newCorner;
		movePieces(w, newFace, newSide, newDirection, CORNER, 2, fast);
		setPuzzleMove(w, ACTION_CONTROL, newFace, newSide,
			newDirection, CORNER, 2, fast);
	} else if (style == MIDDLE) {
		newSide = (position + 2) % MAX_ORIENT;
		movePieces(w, face, newSide, direction, CORNER, 2, fast);
		setPuzzleMove(w, ACTION_CONTROL, face, newSide, direction,
			CORNER, 2, fast);
		corner = (position - ((((position + direction) & 1) ==
			0) ? 1 : 0) + MAX_ORIENT) % MAX_ORIENT;
		newCorner = oppCorner[face][corner].rotation;
		newFace = oppCorner[face][corner].face;
		if (newFace == 1 || newFace == 3)
			newDirection = direction;
		else
			newDirection = (direction + 2) % MAX_ORIENT;
		newDirection = 2 * (newDirection / 2) +
			(((newDirection & 1) == 0) ? 1 : 0);
		newSide = (newCorner + 2) % MAX_ORIENT;
		movePieces(w, newFace, newSide, newDirection, CORNER, 2, fast);
		setPuzzleMove(w, ACTION_CONTROL, newFace, newSide,
			newDirection, CORNER, 2, fast);
	} else if (style == EDGE) {
		newSide = (position + 2) % MAX_ORIENT;
		movePieces(w, face, newSide, direction, EDGE, 2, fast);
		setPuzzleMove(w, ACTION_CONTROL, face, newSide, direction,
			EDGE, 2, fast);
	}
}

static void
moveAltCb(DinoWidget w, int face, int position, int direction, int style,
		int fast)
{
	int newFace, newSide, newDirection, corner, newCorner;

	movePieces(w, face, position, direction, style, 1, fast);
	setPuzzleMove(w, ACTION_CONTROL, face, position, direction, style, 1, fast);
	newSide = (position + 2) % MAX_ORIENT;
	movePieces(w, face, newSide, direction, CORNER, 2, fast);
	setPuzzleMove(w, ACTION_CONTROL, face, newSide, direction,
		CORNER, 2, fast);
	corner = (position - ((((position + direction) & 1) ==
		0) ? 1 : 0) + MAX_ORIENT) % MAX_ORIENT;
	newCorner = oppCorner[face][corner].rotation;
	newFace = oppCorner[face][corner].face;
	if (newFace == 1 || newFace == 3)
		newDirection = direction;
	else
		newDirection = (direction + 2) % MAX_ORIENT;
	newDirection = 2 * (newDirection / 2) +
		(((newDirection & 1) == 0) ? 1 : 0);
	newSide = (newCorner + 2) % MAX_ORIENT;
	movePieces(w, newFace, newSide, newDirection, CORNER, 2, fast);
	setPuzzleMove(w, ACTION_CONTROL, newFace, newSide,
		newDirection, CORNER, 2, fast);
}

void
movePuzzle(DinoWidget w, int face, int position, int direction, int style,
		int control, int fast)
{
	if (control == 2)
		moveAltCb(w, face, position, direction, style, fast);
	else if (control == 1)
		moveControlCb(w, face, position, direction, style, fast);
	else {
		movePieces(w, face, position, direction, style, 0, fast);
		setPuzzleMove(w, ACTION_MOVED, face, position, direction,
			style, 0, fast);
	}
#ifdef USE_SOUND
	if (w->dino.sound) {
		playSound(MOVESOUND);
	}
#endif
	setMove(&undo, direction, style, control, face, position);
	flushMoves(w, &redo, FALSE);
}

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

static Boolean
selectPieces(DinoWidget w, int x, int y, int *face, int *position)
{
	if (w->dino.dim == 2)
		return selectPieces2D((Dino2DWidget) w, x, y,
			face, position);
	else if (w->dino.dim == 3)
		return selectPieces3D((Dino3DWidget) w, x, y,
			face, position);
#ifdef HAVE_OPENGL
	else if (w->dino.dim == 4)
		return selectPiecesGL((DinoGLWidget) w, x, y,
			face, position);
#endif
	return False;
}

static int
checkMoveDir(int position1, int position2, int *direction)
{
	if (position1 == position2)
		return 2;
	if (((position1 - position2 + MAX_ORIENT) & 1) == 0)
		return 0;
	switch (position1) {
	case 0:
		*direction = (position2 == 1) ? 1 : 2;
		break;
	case 1:
		*direction = (position2 == 2) ? 2 : 3;
		break;
	case 2:
		*direction = (position2 == 3) ? 3 : 0;
		break;
	case 3:
		*direction = (position2 == 0) ? 0 : 1;
		break;
	default:
		return 0;
	}
	return 1;
}

static Boolean
narrowSelection(DinoWidget w, int *face, int *direction)
{
	if (w->dino.dim == 2)
		return narrowSelection2D((Dino2DWidget) w, face, direction);
	else if (w->dino.dim == 3)
		return narrowSelection3D((Dino3DWidget) w, face, direction);
#ifdef HAVE_OPENGL
	else if (w->dino.dim == 4)
		return narrowSelectionGL((DinoGLWidget) w, face, direction);
#endif
	return False;
}

static Boolean
positionPieces(DinoWidget w, int x, int y, int *face, int *position, int *direction)
{
	if (!selectPieces(w, x, y, face, position))
		return False;
	return narrowSelection(w, face, direction);
}

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

	if (!w->dino.practice && !control && !alt && checkSolved(w)) {
		moveNoPieces(w);
		return;
	}
	if (!positionPieces(w, x, y, &face, &position, &direction))
		return;
	if (direction >= MAX_ORIENT) {
		if (control)
			style = FACE;
		else
			return;
	} else
		style = getStyle(w, shift, control, alt);
	if (alt)
		control = 2;
	else
		control = (control) ? 1 : 0;
	if (direction == CW || direction == CCW) {
		int oldFace = face;
		face = rotateToRow[face].face;
		direction = MAX_ORIENT + ((direction == CCW) ?
			(rotateToRow[oldFace].rotation + 2) % MAX_ORIENT :
			rotateToRow[oldFace].rotation);
	}
	movePuzzle(w, face, position, direction, style, control, NORMAL);
	if (!control && checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
resetPieces(DinoWidget w)
{
	int face, position;

	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < MAX_ORIENT; position++) {
			w->dino.cubeLoc[face][position].face = face;
			w->dino.cubeLoc[face][position].rotation = STRT -
				MAX_ORIENT;
		}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->dino.started = False;
	w->dino.currentFace = IGNORE_DIR;
	w->dino.currentDirection = IGNORE_DIR;
}

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

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &mode) != 1) {
		(void) fprintf(stderr,
			"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(&buf1, buf2, BOTH);
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &orient) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for orient\n");
		return;
	}
	if (w->dino.orient != (Boolean) orient) {
		setPuzzle(w, ACTION_ORIENTIZE);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &practice) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for practice\n");
		return;
	}
	if (w->dino.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) fprintf(stderr,
			"corrupt record, expecting an integer for moves\n");
		return;
	}
	if (!scanStartPosition(fp, w))
		return;
	setPuzzle(w, ACTION_RESTORE);
#ifdef WINVER
	setStartPosition(w);
#endif
	if (!scanMoves(fp, w, moves))
		return;
	(void) printf("%s: mode %d, orient %s, practice %s, moves %d\n",
		name, mode, BOOL_STRING(orient), BOOL_STRING(practice), moves);
}

static void
getPieces(DinoWidget 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->dino.cheat = True; /* Assume the worst. */
	setPuzzle(w, ACTION_CHEAT);
}

static void
writePieces(DinoWidget 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, "mode%c %d\n", SYMBOL, w->dino.mode);
	(void) fprintf(fp, "orient%c %d\n", SYMBOL, (w->dino.orient) ? 1 : 0);
	(void) fprintf(fp, "practice%c %d\n", SYMBOL,
		(w->dino.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(DinoWidget w)
{
	if (madeMoves(&undo)) {
		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 :
			5 * MAX_ORIENT - direction;
		if (control == 2)
			moveAltCb(w, face, position, direction, style, DOUBLE);
		else if (control == 1)
			moveControlCb(w, face, position, direction, style, DOUBLE);
		else {
			movePieces(w, face, position, direction, style, 0, DOUBLE);
			setPuzzleMove(w, ACTION_UNDO, face, position, direction,
				style, 0, DOUBLE);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
}

static void
redoPieces(DinoWidget w)
{
	if (madeMoves(&redo)) {
		int face, position, direction, style, control;

		getMove(&redo, &direction, &style, &control,
			&face, &position);
		setMove(&undo, direction, style, control,
			face, position);
		if (control == 2)
			moveAltCb(w, face, position, direction, style, DOUBLE);
		else if (control == 1)
			moveControlCb(w, face, position, direction, style, DOUBLE);
		else {
			movePieces(w, face, position, direction, style, 0, DOUBLE);
			setPuzzleMove(w, ACTION_REDO, face, position, direction,
				style, 0, DOUBLE);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
}

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

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

static void
randomizePieces(DinoWidget w)
{
	int face, position, direction, style;
	int big = MAX_ORIENT * 3 + NRAND(2);

	w->dino.cheat = False;
	setPuzzle(w, ACTION_CHEAT);
	if (w->dino.practice)
		practicePieces(w);
	setPuzzle(w, ACTION_RESET);

#ifdef DEBUG
	big = 3;
#endif

	while (big--) {
		face = NRAND(MAX_FACES);
		position = NRAND(MAX_ORIENT);
		direction = ((NRAND(2)) ? position + 1 : position + 3) %
			MAX_ORIENT;
		if (w->dino.mode == PERIOD2)
			style = EDGE;
		else if (w->dino.mode == BOTH)
			style = NRAND(3);
		else
			style = NRAND(2);
		movePuzzle(w, face, position, direction, style, FALSE, INSTANT);
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	setPuzzle(w, ACTION_RANDOMIZE);
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
solvePieces(DinoWidget w)
{
	if (checkSolved(w))
		return;
	solveSomePieces(w);
}

static void
findPieces(DinoWidget w)
{
#ifdef FIND
	if (w->dino.mode == PERIOD2)
		findSomeMoves(w);
	else {
		setPuzzle(w, ACTION_FIND_MESSAGE);
	}
#endif
}

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

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

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

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

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

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

	w->dino.mode = (int) GetPrivateProfileInt(SECTION,
		"mode", DEFAULT_MODE, INIFILE);
	w->dino.orient = (BOOL) GetPrivateProfileInt(SECTION,
		"orient", DEFAULT_ORIENT, INIFILE);
	w->dino.practice = (BOOL) GetPrivateProfileInt(SECTION,
		"practice", DEFAULT_PRACTICE, INIFILE);
#ifdef HAVE_OPENGL
	w->dino.dim = (int) GetPrivateProfileInt(SECTION, "dim", 4, INIFILE);
#else
	w->dino.dim = (int) GetPrivateProfileInt(SECTION, "dim", 3, INIFILE);
#endif
	w->dino.view = (int) GetPrivateProfileInt(SECTION, "view", 1, INIFILE);
	w->dino.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->dino.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));
	/* gray25 */
	w->dino.frameGC = RGB(color.red, color.green, color.blue);
	(void) GetPrivateProfileString(SECTION,
		"pieceBorder", "64 64 64", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->dino.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->dino.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->dino.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->dino.faceChar[face] = szBuf[0];
	}
	w->dino.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", 10, INIFILE);
	w->dino.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", FALSE, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"moveSound", MOVESOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->dino.moveSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->dino.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->dino.scoreFile, szBuf, 80);
}

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

void
resizePuzzle(DinoWidget w)
{
	if (w->dino.dim == 2)
		resizePuzzle2D((Dino2DWidget) w);
	else if (w->dino.dim == 3)
		resizePuzzle3D((Dino3DWidget) w);
#ifdef HAVE_OPENGL
	else if (w->dino.dim == 4)
		resizePuzzleGL((DinoGLWidget) w);
#endif
}

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

void
exposePuzzle(DinoWidget w)
{
	if (w->dino.dim == 2)
		exposePuzzle2D((Dino2DWidget) w);
	else if (w->dino.dim == 3)
		exposePuzzle3D((Dino3DWidget) w);
#ifdef HAVE_OPENGL
	else if (w->dino.dim == 4)
		exposePuzzleGL((DinoGLWidget) w);
#endif
}

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

	valueMask = GCForeground | GCBackground;
	if (w->dino.reverse) {
		values.background = w->dino.foreground;
	} else {
		values.background = w->dino.background;
	}
	if (!w->dino.mono || w->dino.dim == 4) {
		if (!w->dino.faceName[face]) {
			char *buf1;

			intCat(&buf1, "Color name null for face ", face);
			DISPLAY_WARNING(buf1);
			free(buf1);
		} else if (XAllocNamedColor(XtDisplay(w),
				DefaultColormapOfScreen(XtScreen(w)),
				w->dino.faceName[face], &colorCell, &rgb)) {
			values.foreground = w->dino.faceColor[face] =
				colorCell.pixel;
			if (w->dino.faceGC[face])
				XtReleaseGC((Widget) w, w->dino.faceGC[face]);
			w->dino.faceGC[face] = XtGetGC((Widget) w, valueMask,
				&values);
			return;
		} else {
			char *buf1, *buf2;

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

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

	valueMask = GCForeground | GCBackground;

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

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

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

	if (w->dino.menu != ACTION_IGNORE) {
		int menu = w->dino.menu;

		w->dino.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_ORIENTIZE:
			orientizePieces(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->dino.currentDirection == RESTORE_DIR) {
		setStartPosition(w);
		w->dino.currentDirection = IGNORE_DIR;
	} else if (w->dino.currentDirection == CLEAR_DIR) {
		resetPieces(w);
		redraw = True;
		w->dino.currentDirection = IGNORE_DIR;
	} else if (w->dino.currentDirection > IGNORE_DIR) {
		int face = w->dino.currentFace;
		int position = w->dino.currentPosition;
		int direction = w->dino.currentDirection;

		w->dino.currentFace = IGNORE_DIR;
		w->dino.currentPosition = IGNORE_DIR;
		w->dino.currentDirection = IGNORE_DIR;
		movePieces(w, face, position, direction, w->dino.style,
			w->dino.currentControl, w->dino.currentFast);
	}
	return redraw;
}

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

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

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

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

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

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

void
selectPuzzle(DinoWidget w
#ifdef WINVER
, const int x, const int y, const int control, const int alt
#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);
	int alt = (int) (event->xkey.state & (Mod1Mask | Mod3Mask | Mod4Mask | Mod5Mask));
#endif

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

void
releasePuzzle(DinoWidget w
#ifdef WINVER
, const int x, const int y, const int shift, const int control, const int alt
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
#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);
	int alt = (int) (event->xkey.state & (Mod1Mask | Mod3Mask | Mod4Mask | Mod5Mask));
#endif
	int style, face, position, count = -1;
	int direction = -1, ctrl = control;

	if (w->dino.currentFace <= IGNORE_DIR)
		return;
	drawTriangle(w, w->dino.currentFace, w->dino.currentPosition, FALSE);
	style = getStyle(w, shift, control, alt);
	if (!control && !alt && !w->dino.practice && checkSolved(w))
		moveNoPieces(w);
	else if (selectPieces(w, x, y, &face, &position)) {
		if (alt)
			ctrl = 2;
		else
			ctrl = (ctrl) ? 1 : 0;
		if (face == w->dino.currentFace) {
			count = checkMoveDir(w->dino.currentPosition,
				position, &direction);
			/* count == 2 too annoying */
			if (count == 2)
				count = -1;
		} else {
			direction = facesToDirection[w->dino.currentFace][face][w->dino.currentPosition];
			if (direction == AMBIGUOUS_DIR &&
					(style == CORNER || style == MIDDLE)) {
				/* minimize ambiguity */
				count = face;
				face = w->dino.currentFace;
				w->dino.currentFace = count;
				w->dino.currentPosition = position;
				direction = facesToDirection[w->dino.currentFace][face][w->dino.currentPosition];
				if (direction >= 0)
					direction = (direction + MAX_ORIENT / 2) % MAX_ORIENT;
			}
			count = (direction == AMBIGUOUS_DIR) ? 2 : 1;
			if (direction == IGNORE_DIR)
				count = 0;
		}
		if (count == 1) {
			face = w->dino.currentFace;
			position = w->dino.currentPosition;
			w->dino.currentFace = IGNORE_DIR;
			w->dino.currentPosition = IGNORE_DIR;
			movePuzzle(w, face, position, direction,
				style, ctrl, NORMAL);
			if (!control && checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		} else if (count == 2) {
			setPuzzle(w, ACTION_AMBIGUOUS);
		} else if (count == 0) {
			moveNoPieces(w);
		}
	}
	w->dino.currentFace = IGNORE_DIR;
	w->dino.currentDirection = IGNORE_DIR;
#ifdef HAVE_OPENGL
	if (w->dino.dim == 4 && count != 1) {
		drawAllPiecesGL((DinoGLWidget) w);
	}
#endif
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#else
void
movePuzzleCcw(DinoWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, CCW,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xbutton.state & ControlMask),
		(int) (event->xkey.state & (Mod1Mask | Mod3Mask | Mod4Mask | Mod5Mask)));
}

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