/*
 * @(#)xpyraminx.c
 *
 * Copyright 1993 - 2022  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.
 */

/* Driver file for Pyraminx */

#ifndef WINVER
#include "version.h"
static const char aboutHelp[] = {
"Pyraminx Version " VERSION "\n"
"Send bugs (reports or fixes) to the author: "
"David Bagley <bagleyd AT verizon.net>\n"
"The latest version is at: "
"https://www.sillycycle.com/puzzles.html"
};

static const char synopsisHelp[] = {
"[-geometry [{width}][x{height}][{+-}{xoff}[{+-}{yoff}]]]\n"
"[-display [{host}]:[{vs}]] [-[no]mono] [-[no]{reverse|rv}]\n"
"[-{foreground|fg} {color}] [-{background|bg} {color}]\n"
"[-face{0|1|2|3} {color}] [-{border|bd} {color}]\n"
"[-delay msecs] [-[no]sound] [-moveSound {filename}]\n"
"[-{font|fn} {fontname}] [-{size {int} | sticky}]\n"
"[-{mode {int} | both}] [-[no]orient] [-[no]practice]\n"
"[-userName {string}] [-scoreFile {filename}] [-scores]\n"
"[-version]"
};
#endif

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA) || defined(WINVER)
static const char descriptionHelp[] = {
"The original puzzle has 9 triangles per face (size = 3) "
"and has period 3 turning (i.e. the face or points turn\n"
"in 120 degree intervals).  The puzzle was designed by Uwe "
"Meffert and called the Pyraminx.  This has 2^5*3^8*6!/2\n"
"or 75,582,720 different combinations.\n"
"Another puzzle Senior Pyraminx 3x3x3 exists only on "
"paper, it has period 2 turning (i.e. edges turn with\n"
"180 degree intervals) but the corners would fall off "
"unless it had some tricky mechanism.  (This may be the\n"
"same as the Master Pyraminx which has 446,965,972,992,000 "
"different combinations).\n"
"Another puzzle (which was not widely distributed), the "
"Junior Pyraminx (and similarly the Junior Pyraminx Star,\n"
"a octahedron formed by two tetrahedra, this has 7!*3^6 or "
"3,674,160 different combinations), has 4 triangles\n"
"(size = 2) per face.  This puzzle has been recently "
"reissued by Meffert as Pyramorphix\n"
"(http://www.mefferts-puzzles.com). At the time I designed "
"this computer puzzle thought that it had only period 2\n"
"turning (i.e the edges rotate).  It turns out the puzzle "
"has a period 4 turning (edges turn with 90 degree\n"
"intervals) which makes it analogous to the 2x2x2 Rubik's "
"cube.  This puzzle makes various non-tetrahedral shapes.\n"
"The puzzle contained here has no period 4 turning "
"flexibility.\n"
"One is able to simulate Halpern's Tetrahedron or Pyraminx "
"Tetrahedron (period 3 turning and sticky mode).   Also one\n"
"is able to simulate one with variant turning (period 2 "
"turning and sticky mode)."
};

static const char featuresHelp[] = {
"Press \"mouse-left\" button to move a piece.  Release "
"\"mouse-left\" button on a piece on the same face and\n"
"in the same row (but not an adjacent piece or the move "
"is ambiguous).  The pieces will then turn towards\n"
"where the mouse button was released.\n"
"\n"
"Click \"mouse-center\" button, or press \"P\" or \"p\" "
"keys, to toggle the practice mode (in practice mode the\n"
"record should say \"practice\").  This is good for learning "
"moves and experimenting.\n"
"\n"
"Click \"mouse-right\" button, or press \"Z\" or \"z\" "
"keys, to randomize the puzzle (this must be done first\n"
"to set a new record).\n"
"\n"
"Press \"G\" or \"g\" keys to read (get) a saved puzzle.\n"
"\n"
"Press \"W\" or \"w\" keys to save (write) a puzzle.\n"
"\n"
"Press \"U\" or \"u\" keys to undo a move.\n"
"\n"
"Press \"R\" or \"r\" keys to redo a move.\n"
"\n"
"Press \"C\" or \"c\" keys to clear the puzzle.\n"
"\n"
"Press \"S\" or \"s\" keys to start auto-solver.  Only works on "
"1x1x1, 2x2x2, and 3x3x3 facets in Period 3 mode.\n"
"\n"
"Press \"O\" or \"o\" keys to toggle the orient mode.  One "
"has to orient the faces in orient mode, besides\n"
"getting all the faces to be the same color.  To do this "
"one has to get the lines to be oriented in\n"
"the same direction, this only matters with center "
"pieces, if at all (i.e. those that are not on a\n"
"corner or edge).  This does add complexity so there are "
"2 sets of records.\n"
"\n"
"Press \"2\", \"3\", \"B\", or \"b\" keys (not the keypad 2, 3) "
"to change modes to Period 2, Period 3, or Both.\n"
"\n"
"Press \"Y\" or \"y\" keys to toggle sticky mode (increase/decrease "
"is disabled here if sticky mode is on).\n"
"\"Sticky\" and \"Period 2\" turning allows only the edges "
"to turn, and the 2 center rows turn together. It is\n"
"as if the middle cut of the three cuts did not exist.\n"
"\"Sticky\" and \"Period 3\" turning allows only the faces to "
"turn, it is as if the middle cut of the three cuts\n"
"did not exist.\n"
"Beware, the \"Sticky\" mode is a hack and much could be done "
"to improve its look.\n"
"\n"
"Press \"I\" or \"i\" keys to increase the number of pieces.\n"
"\n"
"Press \"D\" or \"d\" keys to decrease the number of pieces.\n"
"\n"
"Press \">\" or \".\" keys to speed up the movement of pieces.\n"
"\n"
"Press \"<\" or \",\" keys to slow down the movement of pieces.\n"
"\n"
"Press \"@\" key to toggle the sound.\n"
"\n"
"Press \"Esc\" key to hide program.\n"
"\n"
"Press \"Q\", \"q\", or \"CTRL-C\" keys to kill program.\n"
"\n"
"Use the key pad or arrow keys to move without the mouse.\n"
"Key pad is defined for Pyraminx as:\n"
"  /    Counterclockwise\n"
"  8 9  Up, Upper Right\n"
"  ^\n"
"4<5>6  Left, Clockwise, Right\n"
"  v\n"
"1 2    Lower Left, Down\n"
"\n"
"Use the shift keys to access \"Period 3\" turns from "
"\"Both\" mode, otherwise it assumes \"Period 2\" turning.\n"
"Faces and points turn in \"Period 3\" and edges (2 points) "
"turn in \"Period 2\".\n"
"\n"
"Use the control key and the left mouse button, keypad, "
"or arrow keys to move the whole tetrahedron.  This\n"
"is not recorded as a turn."
};

static const char referencesHelp[] = {
"James G. Nourse, The Simple Solutions to Cubic Puzzles, "
"Bantam Books, New York, 1981. pp 8-15.\n"
"Tom Werneck, Mastering the Magic Pyramid, Evans Brothers "
"Limited, London, 1981. pp 109-111.\n"
"Douglas R. Hofstadter, Beyond Rubik's Cube: spheres, pyramids, "
"dodecahedrons and God knows what else, Scientific American,\n"
"July 1982, pp 16-31.\n"
"John Ewing & Czes Kosniowski, Puzzle it Out: Cubes Groups and "
"Puzzles, Cambridge University Press, New York, 1982, pp 60-61.\n"
"Magic Cubes 1996 Catalog of Dr. Christoph Bandelow."
};
#endif

static const char solveHelp[] = {
"Auto-solver: sorry, only implemented for size < 4 where "
"period = 3."
};

#include "file.h"
#ifdef WINVER
#include "PyraminxP.h"
#define TITLE "wpyraminx"

static PyraminxRec widget;

#ifndef SCOREPATH
#ifdef UNIXDELIM
#define SCOREPATH "c:/WINDOWS"
#else
#define SCOREPATH "c:\\WINDOWS"
#endif
#endif
#define PRINT_MESSAGE(b) (void) MessageBox(widget.core.hWnd, (LPCSTR) b, "Warning", MB_OK);
#define SET_STARTED(w,b) w->pyraminx.started = b
#else
#include "xwin.h"
#include "xgui.h"
#define SET_STARTED(w,b) XtVaSetValues(w, XtNstart, b, NULL)
#include "Pyraminx.h"
#ifdef HAVE_XPM
#include <X11/xpm.h>
#ifdef CONSTPIXMAPS
#include "16x16/pyraminx.xpm"
#include "22x22/pyraminx.xpm"
#include "24x24/pyraminx.xpm"
#include "32x32/pyraminx.xpm"
#include "48x48/pyraminx.xpm"
#include "64x64/pyraminx.xpm"
#else
#include "pixmaps/16x16/pyraminx.xpm"
#include "pixmaps/22x22/pyraminx.xpm"
#include "pixmaps/24x24/pyraminx.xpm"
#include "pixmaps/32x32/pyraminx.xpm"
#include "pixmaps/48x48/pyraminx.xpm"
#include "pixmaps/64x64/pyraminx.xpm"
#endif
#define RESIZE_XPM(s) ((char **) (((s)<=32)?\
(((s)<=22)?(((s)<=16)?pyraminx_16x16:pyraminx_22x22):\
(((s)<=24)?pyraminx_24x24:pyraminx_32x32)):\
(((s)<=48)?pyraminx_48x48:pyraminx_64x64)))
#endif
#include "pixmaps/64x64/pyraminx.xbm"
#define DEFINE_XBM (char *) pyraminx_64x64_bits, pyraminx_64x64_width, pyraminx_64x64_height
#ifndef SCOREPATH
#ifdef VMS
#define SCOREPATH "SYS$LOGIN:"
#else
#define SCOREPATH "/var/games/xpuzzles"
#endif
#endif
#endif

#ifndef SCOREFILE
#define SCOREFILE "pyraminx.scores"
#endif

#define MAX_FACETS 7
#define NEVER (-1)
#define USER_NAME_LENGTH 120
#define MESSAGE_LENGTH (USER_NAME_LENGTH+64)
#define NOACCESS "noaccess"
#define NOBODY "nobody"

typedef struct {
	int score;
	char name[USER_NAME_LENGTH];
} PuzzleRecord;

static PuzzleRecord puzzleRecord[MAX_MODES][2][MAX_FACETS - MIN_FACETS + 2];
static int movesDsp = 0;
static char messageDsp[MESSAGE_LENGTH] = "Welcome";
static char recordDsp[MESSAGE_LENGTH] = "NOT RECORDED";
static int oldSize;

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
static Widget movesText, recordText, message;
static Widget orientToggle, stickyToggle, practiceToggle;
static Widget soundMenuItem;
static Widget practiceDialog, randomizeDialog, solveDialog;
static Widget descriptionDialog, featuresDialog;
static Widget synopsisDialog, referencesDialog, aboutDialog;
static char buff[MESSAGE_LENGTH];
static const char *sizeLabel = "Size:";
static const char *speedLabel = "Animation Speed:";
#define PRINT_MESSAGE(b) printState(message, b)
#define MIN_SPEED 1
#define MAX_SPEED 50
static const char *modeString[] =
{
	"Period 2", "Period 3", "Both"
};
#elif !defined(WINVER)
#define PRINT_MESSAGE(b) XtWarning(b)
#endif
#ifdef HAVE_MOTIF
static Widget modes[MAX_MODES];
static Widget facetChanger, speedChanger;
#elif defined(HAVE_ATHENA)
static Widget modeLabel, modeMenu;
static Widget facetSlider, speedSlider;
static Widget facetSliderLabel, speedSliderLabel;
static const char *fileTypes[] =
{
	"Get",
	"Write",
	"Exit"
};
#define numFileTypes (sizeof(fileTypes)/sizeof(fileTypes[0]))
static const char *playTypes[] =
{
	"Undo",
	"Redo",
	"Clear",
#ifdef EXTRA
	"Practice",
#endif
	"Randomize",
	"Auto-solve",
#ifdef EXTRA
	"Increment",
	"Decrement",
	"Oriented",
	"Sticky",
	"Speed >",
	"Slow <",
#endif
	"Sound @"
};
#define numPlayTypes (sizeof(playTypes)/sizeof(playTypes[0]))
static const char *helpTypes[] =
{
	"Description...",
	"Features...",
	"Synopsis...",
	"References...",
	"About..."
};
#define numHelpTypes (sizeof(helpTypes)/sizeof(helpTypes[0]))
#endif
static char scoreFileName[FILE_NAME_LENGTH] = SCOREFILE;
static char fileName[FILE_NAME_LENGTH];
static Boolean randomized = False;
#ifdef WINVER
#define PROGRAM_NAME_LENGTH 80
static char progDsp[PROGRAM_NAME_LENGTH] = TITLE;
static char userNameDsp[USER_NAME_LENGTH] = "guest";
#else
static Pixmap pixmap = None;
static Widget topLevel, puzzle;
static char userNameDsp[USER_NAME_LENGTH] = "";

#ifdef HAVE_MOTIF
static void
printState(Widget w, char *msg)
{
	XmString xmstr;

	if (!XtIsSubclass(w, xmLabelWidgetClass))
		XtError("printState() requires a Label Widget");
	xmstr = XmStringCreateLtoR(msg, XmSTRING_DEFAULT_CHARSET);
	XtVaSetValues(w,
		XmNlabelString, xmstr, NULL);
	XmStringFree(xmstr);
}
#elif defined(HAVE_ATHENA)
static void
printState(Widget w, char *msg)
{
	XtVaSetValues(w,
		XtNlabel, msg, NULL);
	if (w == movesText)
		XtVaSetValues(w,
			XtNwidth, 64, NULL);
}
#endif

static void
printRecords(void)
{
	int i, mode, orient;

	(void) printf("      PYRAMINX  HALL OF FAME\n\n");
	(void) printf("MODE ORIENT FACET USER       MOVES\n");
	for (mode = 0; mode < MAX_MODES; mode++)
		for (orient = 0; orient < 2; orient++)
			for (i = 0; i <= MAX_FACETS - MIN_FACETS + 1; i++) {
				if (puzzleRecord[mode][orient][i].score > 0)
					(void) printf("%4d%7d%6d %-12s%9d\n",
						mode + PERIOD2, orient, i + 1,
						puzzleRecord[mode][orient][i].name,
						puzzleRecord[mode][orient][i].score);
			}
}
#endif

static void
initRecords(void)
{
	int i, mode, orient;

	for (mode = 0; mode < MAX_MODES; mode++)
		for (orient = 0; orient < 2; orient++)
			for (i = 0; i <= MAX_FACETS - MIN_FACETS + 1; i++) {
				puzzleRecord[mode][orient][i].score = NEVER;
				(void) strncpy(puzzleRecord[mode][orient][i].name,
					NOACCESS, USER_NAME_LENGTH);
			}
}

static void
readRecords(void)
{
	FILE *fp;
	int i, n, mode, orient;
	char userName[USER_NAME_LENGTH];
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname;

	stringCat(&buf1, CURRENTDELIM, scoreFileName);
	lname = buf1;
	stringCat(&buf1, SCOREPATH, FINALDELIM);
	stringCat(&buf2, buf1, SCOREFILE);
	free(buf1);
	fname = buf2;
	(void) strncpy(fileName, lname, USER_NAME_LENGTH);
	if ((fp = fopen(fileName, "r")) == NULL) {
		(void) strncpy(fileName, fname, USER_NAME_LENGTH);
		/* Try installed directory. */
		if ((fp = fopen(fileName, "r")) == NULL) {
			stringCat(&buf1, "Cannot read ", fname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, lname);
			free(buf2);
			PRINT_MESSAGE(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot read ", fname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, lname);
			free(buf2);
			PRINT_MESSAGE(buf1);
			free(buf1);
		}
#endif
	}
	free(lname);
	free(fname);
	for (mode = 0; mode < MAX_MODES; mode++)
		for (orient = 0; orient < 2; orient++)
			for (i = 0; i <= MAX_FACETS - MIN_FACETS + 1; i++) {
				if (fscanf(fp, "%d %s\n", &n, userName) != 2) {
					(void) fprintf(stderr,
						"corrupt record, expecting integer and name for %d %d %d\n",
						mode, orient, i);
					(void) fclose(fp);
					return;
				}
				if (n <= puzzleRecord[mode][orient][i].score ||
						puzzleRecord[mode][orient][i].score <= NEVER) {
					puzzleRecord[mode][orient][i].score = n;
					(void) strncpy(puzzleRecord[mode][orient][i].name,
						userName, USER_NAME_LENGTH);
				}
			}
	(void) fclose(fp);
}

static void
writeRecords(void)
{
	FILE *fp;
	int i, mode, orient;
	char *buf1 = NULL;

	if ((fp = fopen(fileName, "w")) == NULL) {
		stringCat(&buf1, "Cannot write to ", fileName);
		PRINT_MESSAGE(buf1);
		free(buf1);
		return;
	}
	{
#if HAVE_FCNTL_H
		int lfd;
		char lockFile[FILE_NAME_LENGTH];

		(void) strncpy(lockFile, fileName, FILE_NAME_LENGTH - 6);
		lockFile[FILE_NAME_LENGTH - 6] = '\0';
		(void) strncat(lockFile, ".lock", 6);
		while (((lfd = open(lockFile, O_CREAT | O_EXCL, 0644)) < 0) &&
				errno == EEXIST)
			(void) sleep(1);
		if (lfd < 0) {
#if 1
			(void) fprintf(stderr,
				"Lock file exists... guessing its an old one.\n");
#else
			(void) fprintf(stderr,
				"Lock file exists... score not recorded - sorry.\n");
			return;
#endif
		}
#endif
		for (mode = 0; mode < MAX_MODES; mode++) {
			for (orient = 0; orient < 2; orient++) {
				for (i = 0; i <= MAX_FACETS - MIN_FACETS + 1; i++)
					(void) fprintf(fp, "%d %s\n",
						puzzleRecord[mode][orient][i].score,
						puzzleRecord[mode][orient][i].name);
				if (orient < 1 || mode < MAX_MODES - 1)
					(void) fprintf(fp, "\n");
			}
			if (mode < MAX_MODES - 1)
				(void) fprintf(fp, "\n");
		}
#if HAVE_FCNTL_H
		(void) close(lfd);
		(void) unlink(lockFile);
#endif
		(void) fclose(fp);
	}
}

static void
printRecord(int size, int mode,
		Boolean orient, Boolean sticky, Boolean practice)
{
	int i = mode - PERIOD2;
	int j = (orient) ? 1 : 0;
	int k = (sticky) ? MAX_FACETS - MIN_FACETS + 1 : size - MIN_FACETS;

	if (practice) {
		(void) strncpy(recordDsp, "practice", 9);
	} else if (!sticky && size > MAX_FACETS) {
		(void) strncpy(recordDsp, "NOT RECORDED", 13);
	} else if (puzzleRecord[i][j][k].score <= NEVER) {
		(void) strncpy(recordDsp, "NEVER ", 7);
		(void) strncat(recordDsp, NOACCESS, strlen(NOACCESS) + 1);
	} else {
#ifdef HAVE_SNPRINTF
		(void) snprintf(recordDsp, MESSAGE_LENGTH, "%d %s",
			puzzleRecord[i][j][k].score,
			puzzleRecord[i][j][k].name);
#else
		(void) sprintf(recordDsp, "%d %s",
			puzzleRecord[i][j][k].score,
			puzzleRecord[i][j][k].name);
#endif
	}
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	printState(recordText, recordDsp);
#endif
}

static void
printStatus(char *msg, int nMoves
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		, int mode, int size, Boolean sticky
#endif
		)
{
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	printState(message, msg);
	intCpy(buff, nMoves);
	printState(movesText, buff);
#else
	char titleDsp[TITLE_LENGTH] = "";
	char ss[10], mb[10];

	if (sticky)
		(void) strncpy(ss, "sticky", 7);
	else
		intCpy(ss, size);
	if (mode == BOTH)
		(void) strncpy(mb, "both", 5);
	else
		intCpy(mb, mode);
#ifdef HAVE_SNPRINTF
	(void) snprintf(titleDsp, TITLE_LENGTH,
		"%s.%s: %s@ (%d/%s) - %s",
		progDsp, mb, ss, nMoves, recordDsp, msg);
#else
	(void) sprintf(titleDsp,
		"%s.%s: %s@ (%d/%s) - %s",
		progDsp, mb, ss, nMoves, recordDsp, msg);
#endif
#ifdef WINVER
	SetWindowText(widget.core.hWnd, (LPSTR) titleDsp);
#else
	XtVaSetValues(XtParent(puzzle),
		XtNtitle, titleDsp, NULL);
#endif
#endif
}

static Boolean
handleSolved(int counter, int size, int mode, Boolean orient, Boolean sticky)
{
	int i = mode - PERIOD2;
	int j = (orient) ? 1 : 0;
	int k = (sticky) ? MAX_FACETS - MIN_FACETS + 1 : size - MIN_FACETS;

	if ((sticky || size <= MAX_FACETS) &&
			(counter < puzzleRecord[i][j][k].score ||
			puzzleRecord[i][j][k].score <= NEVER)) {
		readRecords();	/* Maybe its been updated by another */
		puzzleRecord[i][j][k].score = counter;
		(void) strncpy(puzzleRecord[i][j][k].name, userNameDsp,
			USER_NAME_LENGTH);
		if (size < 4 || mode == PERIOD2 || (orient &&
				(counter < puzzleRecord[i][!j][k].score ||
				puzzleRecord[i][!j][k].score <= NEVER))) {
			puzzleRecord[i][!j][k].score = counter;
			(void) strncpy(puzzleRecord[i][!j][k].name, userNameDsp,
				USER_NAME_LENGTH);
		}
		writeRecords();
		printRecord(size, mode, orient, sticky, False);
		return True;
	}
	return False;
}

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
static int
getSpeed(int delay)
{
	return MAX_SPEED + MIN_SPEED - delay - 1;
}

static int
getDelay(int speed)
{
	return MAX_SPEED + MIN_SPEED - speed - 1;
}

static void
orientCallback(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
		XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		int state
#endif
		)
{
	int size, mode;
	Boolean orient, sticky, practice, old;

#ifdef HAVE_MOTIF
	orient = cbs->set;
#elif defined(HAVE_ATHENA)
	orient = state;
#endif
	XtVaGetValues(puzzle,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &old,
		XtNsticky, &sticky,
		XtNpractice, &practice, NULL);
	if (old == orient)
		return;
	XtVaSetValues(puzzle,
		XtNorient, orient, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(size, mode, orient, sticky, practice);
	messageDsp[0] = '\0';
	printState(message, messageDsp);
}

static void
stickyCallback(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
		XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		int state
#endif
		)
{
	int size, mode;
	Boolean orient, sticky, practice;

#ifdef HAVE_MOTIF
	sticky = cbs->set;
#elif defined(HAVE_ATHENA)
	sticky = state;
#endif
	XtVaGetValues(puzzle,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &orient,
		XtNpractice, &practice, NULL);
	if ((sticky && size != 4) || (!sticky && oldSize != 4)) {
		if (sticky)
			size = 4;
		else
			size = oldSize;
		if (size <= MAX_FACETS) {
#if HAVE_MOTIF
#ifdef USE_SPIN
			XtVaSetValues(facetChanger,
				XmNposition, size, NULL);
#else
			XmScaleSetValue(facetChanger, size);
#endif
#elif defined(HAVE_ATHENA)
			setScale(facetSlider, facetSliderLabel, size,
				MIN_FACETS, MAX_FACETS, False);
#endif
		}
		XtVaSetValues(puzzle,
			XtNsize, size, NULL);
	}
	XtVaSetValues(puzzle,
		XtNsticky, sticky, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(size, mode, orient, sticky, practice);
	messageDsp[0] = '\0';
	printState(message, messageDsp);
}

static void
practiceCallback(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
		XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		int state
#endif
		)
{
	int size, mode;
	Boolean orient, sticky, practice, old;

#ifdef HAVE_MOTIF
	practice = cbs->set;
#elif defined(HAVE_ATHENA)
	practice = state;
#endif
	XtVaGetValues(puzzle,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &old, NULL);
	if (old == practice)
		return;
	XtVaSetValues(puzzle,
		XtNpractice, practice,
		XtNstart, False, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(size, mode, orient, sticky, practice);
	if (practice)
		messageDsp[0] = '\0';
	else
		(void) strncpy(messageDsp, "Randomize (z) to start",
			MESSAGE_LENGTH);
	printState(message, messageDsp);
}

#if defined(HAVE_ATHENA) && defined(USE_SPIN)
static void
pieceUpCallback(Widget w, XtPointer clientData, XtPointer callData)
{
	int size;

	XtVaGetValues(puzzle,
		XtNsize, &size, NULL);
	size++;
	setScale(facetSlider, facetSliderLabel, size,
		MIN_FACETS, MAX_FACETS, False);
	XtVaSetValues(puzzle,
		XtNsize, size, NULL);
}

static void
pieceDownCallback(Widget w, XtPointer clientData, XtPointer callData)
{
	int size;

	XtVaGetValues(puzzle,
		XtNsize, &size, NULL);
	size--;
	if (size < 1) {
		return;
	}
	setScale(facetSlider, facetSliderLabel, size,
		MIN_FACETS, MAX_FACETS, False);
	XtVaSetValues(puzzle,
		XtNsize, size, NULL);
}

#else

static void
pieceChangeCallback(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
#elif defined(HAVE_ATHENA)
		XtPointer callData
#endif
		)
{
	int size, mode;
	Boolean orient, sticky, practice;

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	int limit;
	size = cbs->position;
#else
	size = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	if (!thumbScroll(callData, &size,
			MIN_FACETS, MAX_FACETS, SCROLL_SIZE)) {
		return;
	}
#endif
	XtVaGetValues(puzzle,
		XtNsize, &oldSize,
		XtNmode, &mode,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &practice, NULL);
	if (sticky) {
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		XtVaSetValues(w,
			XmNposition, oldSize, NULL);
#else
		XmScaleSetValue(w, oldSize);
#endif
#elif defined(HAVE_ATHENA)
		setScale(facetSlider, facetSliderLabel, oldSize,
			MIN_FACETS, MAX_FACETS, False);
#endif
		return;
	} else if (oldSize == size)
		return;
#if defined(HAVE_MOTIF) && defined(USE_SPIN)
	XtVaGetValues(facetChanger,
		XmNmaximumValue, &limit, NULL);
	if (size >= limit)
		XtVaSetValues(facetChanger,
			XmNmaximumValue, size + 1,
			XmNposition, size, NULL);
#elif defined(HAVE_ATHENA)
	setScale(w, label, size, MIN_FACETS, MAX_FACETS, False);
#endif
	oldSize = size;
	XtVaSetValues(puzzle,
		XtNsize, size, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(size, mode, orient, sticky, practice);
	messageDsp[0] = '\0';
	printState(message, messageDsp);
}
#endif

#if defined(HAVE_ATHENA) && defined(USE_SPIN)
static void
speedUpCallback(Widget w, XtPointer clientData, XtPointer callData)
{
	int delay, speed;

	XtVaGetValues(puzzle,
		XtNdelay, &delay, NULL);
	speed = getSpeed(delay);
	speed++;
	if (speed > MAX_SPEED) {
		return;
	}
	setScale(speedSlider, speedSliderLabel, speed,
		MIN_SPEED, MAX_SPEED, True);
	delay = getDelay(speed);
	XtVaSetValues(puzzle,
		XtNdelay, delay, NULL);
}

static void
speedDownCallback(Widget w, XtPointer clientData, XtPointer callData)
{
	int delay, speed;

	XtVaGetValues(puzzle,
		XtNdelay, &delay, NULL);
	speed = getSpeed(delay);
	speed--;
	if (speed < MIN_SPEED) {
		return;
	}
	setScale(speedSlider, speedSliderLabel, speed,
		MIN_SPEED, MAX_SPEED, True);
	delay = getDelay(speed);
	XtVaSetValues(puzzle,
		XtNdelay, delay, NULL);
}

#else

static void
speedChangeCallback(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
#elif defined(HAVE_ATHENA)
		XtPointer callData
#endif
		)
{
	int delay, speed, old;

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	speed = cbs->position;
#else
	speed = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	if (!thumbScroll(callData, &speed,
			MIN_SPEED, MAX_SPEED, SCROLL_SIZE)) {
		return;
	}
#endif
	delay = getDelay(speed);
	XtVaGetValues(puzzle,
		XtNdelay, &old, NULL);
	if (old == delay)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, speed, MIN_SPEED, MAX_SPEED, True);
#endif
	XtVaSetValues(puzzle,
		XtNdelay, delay, NULL);
}
#endif

static void
modeCallback(Widget w,
#ifdef HAVE_MOTIF
		int position, XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		XtPointer clientData, XtPointer callData
#endif
		)
{
	Boolean orient, sticky, practice;
	int size, mode, old;
#ifdef HAVE_MOTIF
	if (!cbs->set)
		return;
#elif defined(HAVE_ATHENA)
	int position = (size_t) clientData;
#endif

	XtVaGetValues(puzzle,
		XtNsize, &size,
		XtNmode, &old,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &practice, NULL);
	mode = position + PERIOD2;
	if (old == mode)
		return;
#ifdef HAVE_ATHENA
	XtVaSetValues(modeLabel,
		XtNlabel, modeString[position], NULL);
#endif
	XtVaSetValues(puzzle,
		XtNmode, mode, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(size, mode, orient, sticky, practice);
	messageDsp[0] = '\0';
	printState(message, messageDsp);
}

static void
puzzlePracticeCallback(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
		XmAnyCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		XtPointer callData
#endif
		)
{
#ifdef HAVE_MOTIF
	if (cbs->reason == XmCR_OK)
#elif defined(HAVE_ATHENA)
	XtPopdown(practiceDialog);
#endif
		XtVaSetValues(puzzle,
			XtNmenu, ACTION_PRACTICE, NULL);
}

static void
puzzleRandomizeCallback(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
		XmAnyCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		XtPointer callData
#endif
		)
{
#ifdef HAVE_MOTIF
	if (cbs->reason == XmCR_OK)
#elif defined(HAVE_ATHENA)
	XtPopdown(randomizeDialog);
#endif
		XtVaSetValues(puzzle,
			XtNmenu, ACTION_RANDOMIZE, NULL);
}

static void
fileMenuCallback(Widget w, XtPointer clientData, XtPointer callData)
{
	int value = (int) ((size_t) clientData) + ACTION_GET;

	if (value == ACTION_EXIT)
		exit(0);
	XtVaSetValues(puzzle,
		XtNmenu, value, NULL);
}

static void
setSoundCheck(Widget w)
{
	Boolean sound;
	XtVaGetValues(puzzle,
		XtNsound, &sound, NULL);
#ifdef HAVE_MOTIF
	XmToggleButtonSetState(w, sound, False);
#elif defined(HAVE_ATHENA)
	menuCheck(topLevel, w, sound);
#endif
}

static void
playMenuCallback(Widget w, XtPointer clientData, XtPointer callData)
{
	int value = (int) ((size_t) clientData) + ACTION_UNDO;

#ifndef EXTRA
	if (value >= ACTION_PRACTICE)
		value += ACTION_RANDOMIZE - ACTION_PRACTICE;
	if (value >= ACTION_INCREMENT)
		value += ACTION_SOUND - ACTION_INCREMENT;
#endif
	XtVaSetValues(puzzle,
		XtNmenu, value, NULL);
	if (value == ACTION_SOUND)
		setSoundCheck(w);
}

static void
helpMenuCallback(Widget w, XtPointer clientData, XtPointer callData)
{
	int value = (int) ((size_t) clientData);
	Widget dialog;

#ifdef HAVE_ATHENA
	if (wmDeleteWindow == None)
		wmDeleteWindow = XInternAtom(XtDisplay(topLevel),
			"WM_DELETE_WINDOW", FALSE);
#endif
	switch (value) {
	case 0:
		dialog = descriptionDialog;
		break;
	case 1:
		dialog = featuresDialog;
		break;
	case 2:
		dialog = synopsisDialog;
		break;
	case 3:
		dialog = referencesDialog;
		break;
	case 4:
		dialog = aboutDialog;
		break;
	default:
		{
			char *buf;

			intCat(&buf, "helpMenuCallback: %d", value);
			XtWarning(buf);
			free(buf);
			return;
		}
	}
#ifdef HAVE_MOTIF
	XtManageChild(dialog);
#elif defined(HAVE_ATHENA)
	XtPopup(dialog, XtGrabNone);
	XSetWMProtocols(XtDisplay(topLevel),
		XtWindow(dialog), &wmDeleteWindow, 1);
#endif
}
#endif

static void
initialize(
#ifdef WINVER
PyraminxWidget w, HBRUSH brush
#else
Widget w
#endif
)
{
	int size, mode;
	Boolean orient, sticky, practice;
	char *userName, *scoreFile;

#ifdef WINVER
	initializePuzzle(w, brush);

	size = w->pyraminx.size;
	mode = w->pyraminx.mode;
	orient = w->pyraminx.orient;
	sticky = w->pyraminx.sticky;
	practice = w->pyraminx.practice;
	userName = w->pyraminx.userName;
	scoreFile = w->pyraminx.scoreFile;
	if (strcmp(scoreFile, "")) {
		(void) strncpy(scoreFileName, scoreFile, FILE_NAME_LENGTH - 1);
		scoreFileName[FILE_NAME_LENGTH - 1] = '\0';
	}
#else
	Boolean scoreOnly, versionOnly;

	XtVaGetValues(w,
		XtNuserName, &userName,
		XtNscoreFile, &scoreFile,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &practice,
		XtNscoreOnly, &scoreOnly,
		XtNversionOnly, &versionOnly, NULL);
	if (versionOnly) {
		(void) printf("%s\n", aboutHelp);
		exit(0);
	}
	if (strcmp(scoreFile, "")) {
		(void) strncpy(scoreFileName, scoreFile, FILE_NAME_LENGTH - 1);
		scoreFileName[FILE_NAME_LENGTH - 1] = '\0';
	}
	if (scoreOnly) {
		initRecords();
		readRecords();
		printRecords();
		exit(0);
	}
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	{
		int limit;
		XtVaGetValues(facetChanger,
			XmNmaximumValue, &limit, NULL);
		if (size >= limit)
			XtVaSetValues(facetChanger,
				XmNposition, size,
				XmNmaximumValue, size + 1, NULL);
		else
			XtVaSetValues(facetChanger,
				XmNposition, size, NULL);
	}
#else
	if (size > MAX_FACETS)
		XtVaSetValues(facetChanger,
			XmNmaximum, size, NULL);
	XmScaleSetValue(facetChanger, size);
#endif
	XmToggleButtonSetState(modes[mode - PERIOD2], True, False);
	XmToggleButtonSetState(orientToggle, orient, True);
	XmToggleButtonSetState(stickyToggle, sticky, True);
	XmToggleButtonSetState(practiceToggle, practice, True);
	{
		int delay, speed;
		XtVaGetValues(w,
			XtNdelay, &delay, NULL);
		speed = getSpeed(delay);
#ifdef USE_SPIN
		XtVaSetValues(speedChanger,
			XmNposition, speed, NULL);
#else
		XmScaleSetValue(speedChanger, speed);
#endif
	}
	setSoundCheck(soundMenuItem);
#elif defined(HAVE_ATHENA)
	XtVaSetValues(orientToggle,
		XtNstate, orient, NULL);
	XtVaSetValues(stickyToggle,
		XtNstate, sticky, NULL);
	XtVaSetValues(practiceToggle,
		XtNstate, practice, NULL);
	setScale(facetSlider, facetSliderLabel, size,
		MIN_FACETS, MAX_FACETS, False);
	{
		int delay, speed;
		XtVaGetValues(w,
			XtNdelay, &delay, NULL);
		speed = getSpeed(delay);
		setScale(speedSlider, speedSliderLabel, speed,
			MIN_SPEED, MAX_SPEED, True);
	}
	setSoundCheck(soundMenuItem);
#endif
#endif
	SET_STARTED(w, False);
	initRecords();
	readRecords();
#ifndef WINVER
	(void) strncpy(userNameDsp, userName, USER_NAME_LENGTH - 1);
	userNameDsp[USER_NAME_LENGTH - 1] = '\0';
#endif
	if (!strcmp(userName, "") || !strcmp(userName, "(null)") ||
			!strcmp(userName, NOACCESS) ||
			!strcmp(userName, NOBODY)) {
#ifdef WINVER
		(void) strncpy(userNameDsp, userName, USER_NAME_LENGTH - 1);
		userNameDsp[USER_NAME_LENGTH - 1] = '\0';
#else
		char *login;

#ifdef HAVE_CUSERID
		login = cuserid(NULL);
#else
		login = getlogin();
#endif
		if (login == NULL) {
			userNameDsp[0] = '\0';
		} else {
			(void) strncpy(userNameDsp, login, USER_NAME_LENGTH - 1);
			userNameDsp[USER_NAME_LENGTH - 1] = '\0';
		}
		if (!strcmp(userNameDsp, "") ||
				!strcmp(userNameDsp, "(null)") ||
				!strcmp(userNameDsp, NOACCESS) ||
				!strcmp(userNameDsp, NOBODY))
			/* It really IS nobody */
			(void) strncpy(userNameDsp, "guest", 6);
#endif
	}
	printRecord(size, mode, orient, sticky, practice);
	oldSize = size;
	printStatus(messageDsp, movesDsp
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		, mode, size, sticky
#endif
		);
}

#ifdef WINVER
void
setPuzzle(PyraminxWidget w, int reason)
#else
static void
puzzleCallback(Widget w, caddr_t clientData,
		pyraminxCallbackStruct *callData)
#endif
{
#ifndef WINVER
	int reason = callData->reason;
	Boolean start;
#endif
#if defined(HAVE_MOTIF) && defined(USE_SPIN)
	int limit;
#elif defined(HAVE_ATHENA)
	int max;
	char *defaultString;
#endif
	int size, mode;
	Boolean orient, sticky, practice, cheat;

	messageDsp[0] = '\0';
#ifdef WINVER
	size = w->pyraminx.size;
	mode = w->pyraminx.mode;
	orient = w->pyraminx.orient;
	sticky = w->pyraminx.sticky;
	practice = w->pyraminx.practice;
	cheat = w->pyraminx.cheat;
	/*start = w->pyraminx.started;*/
#else
	XtVaGetValues(w,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &practice,
		XtNstart, &start,
		XtNcheat, &cheat, NULL);
#endif
	switch (reason) {
	case ACTION_HIDE:
#ifdef WINVER
		ShowWindow(w->core.hWnd, SW_SHOWMINIMIZED);
#else
		(void) XIconifyWindow(XtDisplay(topLevel),
			XtWindow(topLevel),
			XScreenNumberOfScreen(XtScreen(topLevel)));
#endif
		break;
#ifndef WINVER
	case ACTION_PRACTICE_QUERY:
#ifdef HAVE_MOTIF
		XtManageChild(practiceDialog);
#elif defined(HAVE_ATHENA)
		XtPopup(practiceDialog, XtGrabNone);
		XSetWMProtocols(XtDisplay(topLevel),
			XtWindow(practiceDialog), &wmDeleteWindow, 1);
#else
		XtVaSetValues(puzzle,
			XtNmenu, ACTION_PRACTICE, NULL);
#endif
		break;
	case ACTION_RANDOMIZE_QUERY:
#ifdef HAVE_MOTIF
		XtManageChild(randomizeDialog);
#elif defined(HAVE_ATHENA)
		XtPopup(randomizeDialog, XtGrabNone);
		XSetWMProtocols(XtDisplay(topLevel),
			XtWindow(randomizeDialog), &wmDeleteWindow, 1);
#else
		XtVaSetValues(puzzle,
			XtNmenu, ACTION_RANDOMIZE, NULL);
#endif
		break;
#endif
	case ACTION_SOLVE_MESSAGE:
#ifdef WINVER
		(void) MessageBox(w->core.hWnd, solveHelp,
			"Auto-solve", MB_OK);
#else
#ifdef HAVE_MOTIF
		XtManageChild(solveDialog);
#elif defined(HAVE_ATHENA)
		XtPopup(solveDialog, XtGrabNone);
		XSetWMProtocols(XtDisplay(topLevel),
			XtWindow(solveDialog), &wmDeleteWindow, 1);
#else
		(void) strncpy(messageDsp, solveHelp, MESSAGE_LENGTH);
#endif
#endif
		break;
	case ACTION_RESTORE:
		if (practice) {
			(void) strncpy(recordDsp, "practice",
				MESSAGE_LENGTH);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
			printState(recordText, recordDsp);
#endif
		}
		movesDsp = 0;
		randomized = False;
		break;
	case ACTION_RESET:
		movesDsp = 0;
		randomized = False;
		break;
	case ACTION_AMBIGUOUS:
		(void) strncpy(messageDsp, "Ambiguous move",
			MESSAGE_LENGTH);
		break;
	case ACTION_ILLEGAL:
		if (practice || randomized)
			(void) strncpy(messageDsp, "Illegal move",
				MESSAGE_LENGTH);
		else
			(void) strncpy(messageDsp,
				"Randomize (z) to start", MESSAGE_LENGTH);
		break;
	case ACTION_MOVED:
		movesDsp++;
		SET_STARTED(w, True);
		break;
	case ACTION_CONTROL:
		return;
	case ACTION_SOLVED:
		if (practice) {
			movesDsp = 0;
		} else if (cheat) {
			(void) strncpy(messageDsp, "No cheating ", 13);
			(void) strncat(messageDsp, userNameDsp, USER_NAME_LENGTH);
			(void) strncat(messageDsp, "!!", 3);
		} else if (handleSolved(movesDsp, size, mode, orient, sticky)) {
			(void) strncpy(messageDsp, "Congratulations ", 17);
			(void) strncat(messageDsp, userNameDsp, USER_NAME_LENGTH);
			(void) strncat(messageDsp, "!!", 3);
		} else {
			(void) strncpy(messageDsp, "Solved!", 8);
		}
		SET_STARTED(w, False);
		randomized = False;
		break;
	case ACTION_COMPUTED:
		SET_STARTED(w, False);
		break;
	case ACTION_PRACTICE:
		movesDsp = 0;
		randomized = False;
		practice = !practice;
		if (!practice)
			(void) strncpy(messageDsp, "Randomize (z) to start",
				MESSAGE_LENGTH);
		printRecord(size, mode, orient, sticky, practice);
#ifdef WINVER
		w->pyraminx.practice = practice;
		w->pyraminx.started = False;
#else
		XtVaSetValues(w,
			XtNpractice, practice,
			XtNstart, False, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(practiceToggle, practice, True);
#elif defined(HAVE_ATHENA)
		XtVaSetValues(practiceToggle,
			XtNstate, practice, NULL);
#endif
#endif
		break;
	case ACTION_RANDOMIZE:
		movesDsp = 0;
		randomized = True;
#ifdef WINVER
		w->pyraminx.practice = False;
		w->pyraminx.started = False;
#else
		XtVaSetValues(w,
			XtNpractice, False,
			XtNstart, False, NULL);
#endif
		break;
	case ACTION_INCREMENT:
		if (!sticky) {
			movesDsp = 0;
			size++;
			oldSize = size;
			printRecord(size, mode, orient, sticky, practice);
#ifdef WINVER
			w->pyraminx.size = size;
#else
			XtVaSetValues(w,
				XtNsize, size, NULL);
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
			XtVaGetValues(facetChanger,
				XmNmaximumValue, &limit, NULL);
			if (size >= limit)
				XtVaSetValues(facetChanger,
					XmNmaximumValue, size + 1,
					XmNposition, size, NULL);
			else
				XtVaSetValues(facetChanger,
					XmNposition, size, NULL);
#else
			if (size > MAX_FACETS)
				XtVaSetValues(facetChanger,
					XmNmaximum, size, NULL);
			XmScaleSetValue(facetChanger, size);
#endif
#elif defined(HAVE_ATHENA)
			setScale(facetSlider, facetSliderLabel, size,
				MIN_FACETS, MAX_FACETS, False);
#endif
#endif
		}
		break;
	case ACTION_DECREMENT:
		if (!sticky) {
			movesDsp = 0;
			size--;
			oldSize = size;
			printRecord(size, mode, orient, sticky, practice);
#ifdef WINVER
			w->pyraminx.size = size;
#else
			XtVaSetValues(w, XtNsize, size, NULL);
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
			if (size > MAX_FACETS)
				XtVaSetValues(facetChanger,
					XmNmaximumValue, size,
					XmNposition, size, NULL);
			else
				XtVaSetValues(facetChanger,
					XmNposition, size, NULL);
#else
			if (size > MAX_FACETS)
				XtVaSetValues(facetChanger,
					XmNmaximum, size, NULL);
			XmScaleSetValue(facetChanger, size);
#endif
#elif defined(HAVE_ATHENA)
			setScale(facetSlider, facetSliderLabel, size,
				MIN_FACETS, MAX_FACETS, False);
#endif
#endif
		}
		break;
	case ACTION_ORIENTIZE:
		movesDsp = 0;
		orient = !orient;
		printRecord(size, mode, orient, sticky, practice);
#ifdef WINVER
		w->pyraminx.orient = orient;
#else
		XtVaSetValues(w,
			XtNorient, orient, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(orientToggle, orient, True);
#elif defined(HAVE_ATHENA)
		XtVaSetValues(orientToggle,
			XtNstate, orient, NULL);
#endif
#endif
		break;
	case ACTION_PERIOD2:
	case ACTION_PERIOD3:
	case ACTION_BOTH:
		movesDsp = 0;
		mode = reason - ACTION_PERIOD2 + PERIOD2;
		printRecord(size, mode, orient, sticky, practice);
#ifdef WINVER
		w->pyraminx.mode = mode;
#else
		XtVaSetValues(w,
			XtNmode, mode, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(modes[mode - PERIOD2], True, True);
#elif defined(HAVE_ATHENA)
		max = findMaxLength((char **) modeString,
			sizeof(modeString) / sizeof(*modeString));
		createBlank(&defaultString, max, (char *) modeString[mode - PERIOD2], 0);
		XtVaSetValues(modeLabel,
			XtNlabel, defaultString, NULL);
		free(defaultString);
#endif
#endif
		break;
	case ACTION_STICKY:
		movesDsp = 0;
		sticky = !sticky;
		if (sticky)
			size = 4;
		else
			size = oldSize;
		printRecord(size, mode, orient, sticky, practice);
#ifdef WINVER
		w->pyraminx.sticky = sticky;
		w->pyraminx.size = size;
#else
		XtVaSetValues(w,
			XtNsticky, sticky,
			XtNsize, size, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(stickyToggle, sticky, True);
#ifdef USE_SPIN
		XtVaSetValues(facetChanger,
			XmNposition, size, NULL);
#else
		XmScaleSetValue(facetChanger, size);
#endif
#elif defined(HAVE_ATHENA)
		XtVaSetValues(stickyToggle,
			XtNstate, sticky, NULL);
		setScale(facetSlider, facetSliderLabel, size,
			MIN_FACETS, MAX_FACETS, False);
#endif
#endif
		break;
	case ACTION_UNDO:
		movesDsp--;
		SET_STARTED(w, True);
		break;
	case ACTION_REDO:
		movesDsp++;
		SET_STARTED(w, True);
		break;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	case ACTION_SPEED:
		{
			int oldDelay, delay, speed;

			XtVaGetValues(puzzle,
				XtNdelay, &delay, NULL);
			oldDelay = delay;
			if (delay > MAX_SPEED - MIN_SPEED)
				delay = MAX_SPEED - MIN_SPEED;
			if (delay < 0)
				delay = 0;
			if (delay != oldDelay) {
				XtVaSetValues(w,
					XtNdelay, delay, NULL);
			}
			speed = getSpeed(delay);
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
			XtVaSetValues(speedChanger,
				XmNposition, speed, NULL);
#else
			XmScaleSetValue(speedChanger, speed);
#endif
#elif defined(HAVE_ATHENA)
			setScale(speedSlider, speedSliderLabel, speed,
				MIN_SPEED, MAX_SPEED, True);
#endif
		}
		return;
	case ACTION_SOUND:
		setSoundCheck(soundMenuItem);
		break;
#endif
	}
	printStatus(messageDsp, movesDsp
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		, mode, size, sticky
#endif
		);
}

#ifdef WINVER
static LRESULT CALLBACK
about(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message == WM_COMMAND && LOWORD(wParam) == IDOK) {
		(void) EndDialog(hDlg, TRUE);
		return TRUE;
	}
	return FALSE;
}

static LRESULT CALLBACK
WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HBRUSH brush = (HBRUSH) NULL;
	PAINTSTRUCT paint;
	int shift = 0;

	widget.core.hWnd = hWnd;
	if (GetFocus()) {
		if (!widget.pyraminx.focus) {
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_BRUSH));
			enterPuzzle(&widget);
			(void) EndPaint(hWnd, &paint);
		}
	} else {
		if (widget.pyraminx.focus) {
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_BRUSH));
			leavePuzzle(&widget);
			(void) EndPaint(hWnd, &paint);
		}
	}
	switch (message) {
	case WM_CREATE:
		initialize(&widget, brush);
		break;
	case WM_DESTROY:
		destroyPuzzle(brush);
		break;
	case WM_SIZE:
		resizePuzzle(&widget);
		(void) InvalidateRect(hWnd, NULL, TRUE);
		break;
	case WM_PAINT:
		widget.core.hDC = BeginPaint(hWnd, &paint);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		exposePuzzle(&widget);
		(void) EndPaint(hWnd, &paint);
		break;
	case WM_RBUTTONDOWN:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		randomizePuzzle(&widget);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_LBUTTONDOWN:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		selectPuzzle(&widget, LOWORD(lParam), HIWORD(lParam),
			(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_LBUTTONUP:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		releasePuzzle(&widget, LOWORD(lParam), HIWORD(lParam),
			((GetKeyState(VK_SHIFT) >> 1) ||
			(GetKeyState(VK_CAPITAL) & 1)),
			(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
	case WM_MOUSEWHEEL:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		{
			int zDelta = ((short) HIWORD(wParam));
			POINT cursor, origin;

			origin.x = 0, origin.y = 0;
			ClientToScreen(hWnd, &origin);
			(void) GetCursorPos(&cursor);
			if (zDelta > (WHEEL_DELTA >> 1)) {
				movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					TOP,
					((GetKeyState(VK_SHIFT) >> 1) ||
					(GetKeyState(VK_CAPITAL) & 1)),
					(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0);
			} else if (zDelta < -(WHEEL_DELTA >> 1)) {
				movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					BOTTOM,
					((GetKeyState(VK_SHIFT) >> 1) ||
					(GetKeyState(VK_CAPITAL) & 1)),
					(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0);
			}
		}
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
#endif
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case ACTION_GET:
			getPuzzle(&widget);
			resizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_WRITE:
			writePuzzle(&widget);
			break;
		case ACTION_EXIT:
			destroyPuzzle(brush);
			break;
		case ACTION_HIDE:
			hidePuzzle(&widget);
			break;
		case ACTION_UNDO:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			undoPuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_REDO:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			redoPuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_CLEAR:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			clearPuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_PRACTICE:
			practicePuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_RANDOMIZE:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			randomizePuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_SOLVE:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			solvePuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_ORIENTIZE:
			orientizePuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_STICKY:
			(void) stickyModePuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_SHIFT_TOP:
		case ACTION_SHIFT_TR:
		case ACTION_SHIFT_RIGHT:
		case ACTION_SHIFT_BOTTOM:
		case ACTION_SHIFT_BL:
		case ACTION_SHIFT_LEFT:
		case ACTION_SHIFT_CW:
		case ACTION_SHIFT_CCW:
		case ACTION_TOP:
		case ACTION_TR:
		case ACTION_RIGHT:
		case ACTION_BOTTOM:
		case ACTION_BL:
		case ACTION_LEFT:
		case ACTION_CW:
		case ACTION_CCW:
			{
				POINT cursor, origin;

				widget.core.hDC = GetDC(hWnd);
				(void) SelectObject(widget.core.hDC,
					GetStockObject(NULL_PEN));
				origin.x = 0, origin.y = 0;
				ClientToScreen(hWnd, &origin);
				(void) GetCursorPos(&cursor);
				shift = (ACTION_SHIFT(LOWORD(wParam))) ? 1 : 0;
				shift = shift || (GetKeyState(VK_CAPITAL) & 1);
				(void) movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					(int) LOWORD(wParam) -
					((shift) ? ACTION_SHIFT_TOP : ACTION_TOP),
					shift, FALSE);
				(void) ReleaseDC(hWnd, widget.core.hDC);
			}
			break;
		case ACTION_SHIFT_CONTROL_TOP:
		case ACTION_SHIFT_CONTROL_TR:
		case ACTION_SHIFT_CONTROL_RIGHT:
		case ACTION_SHIFT_CONTROL_BOTTOM:
		case ACTION_SHIFT_CONTROL_BL:
		case ACTION_SHIFT_CONTROL_LEFT:
		case ACTION_SHIFT_CONTROL_CW:
		case ACTION_SHIFT_CONTROL_CCW:
		case ACTION_CONTROL_TOP:
		case ACTION_CONTROL_TR:
		case ACTION_CONTROL_RIGHT:
		case ACTION_CONTROL_BOTTOM:
		case ACTION_CONTROL_BL:
		case ACTION_CONTROL_LEFT:
		case ACTION_CONTROL_CW:
		case ACTION_CONTROL_CCW:
			{
				POINT cursor, origin;

				widget.core.hDC = GetDC(hWnd);
				(void) SelectObject(widget.core.hDC,
					GetStockObject(NULL_PEN));
				origin.x = 0, origin.y = 0;
				ClientToScreen(hWnd, &origin);
				(void) GetCursorPos(&cursor);
				shift = (ACTION_SHIFT_CONTROL(LOWORD(wParam))) ? 1 : 0;
				(void) movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					(int) LOWORD(wParam) -
					((shift) ? ACTION_SHIFT_CONTROL_TOP :
					ACTION_CONTROL_TOP),
					shift, TRUE);
				(void) ReleaseDC(hWnd, widget.core.hDC);
			}
			break;
		case ACTION_INCREMENT:
			incrementPuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DECREMENT:
			if (decrementPuzzle(&widget)) {
				sizePuzzle(&widget);
				(void) InvalidateRect(hWnd, NULL, TRUE);
			}
			break;
		case ACTION_PERIOD2:
		case ACTION_PERIOD3:
		case ACTION_BOTH:
			periodModePuzzle(&widget,
				(int) LOWORD(wParam) - ACTION_PERIOD2);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_SPEED:
			speedUpPuzzle(&widget);
			break;
		case ACTION_SLOW:
			slowDownPuzzle(&widget);
			break;
		case ACTION_SOUND:
			toggleSoundPuzzle(&widget);
			break;
		case ACTION_DESCRIPTION:
			(void) MessageBox(hWnd, descriptionHelp,
				"Description", MB_OK | MB_ICONQUESTION);
			break;
		case ACTION_FEATURES:
			(void) MessageBox(hWnd, featuresHelp,
				"Features", MB_OK | MB_ICONEXCLAMATION);
			break;
		case ACTION_REFERENCES:
			(void) MessageBox(hWnd, referencesHelp,
				"References", MB_OK | MB_ICONINFORMATION);
			break;
		case ACTION_ABOUT:
			(void) DialogBox(widget.core.hInstance,
				"About", hWnd, (DLGPROC) about);
			break;
		}
		break;
	default:
		return (DefWindowProc(hWnd, message, wParam, lParam));
	}
	return FALSE;
}

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
		int numCmdShow)
{
	HWND hWnd;
	MSG msg;
	WNDCLASS wc;
	HACCEL hAccel;

	if (!hPrevInstance) {
		wc.style = CS_HREDRAW | CS_VREDRAW;
		wc.lpfnWndProc = WindowProc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hInstance = hInstance;
		wc.hIcon = LoadIcon(hInstance, TITLE);
		wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
		wc.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH);
		wc.lpszMenuName = TITLE;
		wc.lpszClassName = TITLE;
		if (!RegisterClass(&wc))
			return FALSE;
	}
	widget.core.hInstance = hInstance;
	hWnd = CreateWindow(TITLE,
		TITLE,
		WS_OVERLAPPEDWINDOW,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		HWND_DESKTOP,
		(HMENU) NULL,
		hInstance,
		(XtPointer) NULL);
	if (!hWnd)
		return FALSE;
	hAccel = (HACCEL) LoadAccelerators(hInstance, TITLE);
	(void) ShowWindow(hWnd, numCmdShow);
	(void) UpdateWindow(hWnd);
	while (GetMessage(&msg, (HWND) NULL, 0, 0))
		if (!TranslateAccelerator(hWnd, hAccel, &msg)) {
			(void) TranslateMessage(&msg);
			(void) DispatchMessage(&msg);
		}
	return (int) msg.wParam;
}

#else

static XrmOptionDescRec options[] =
{
	{(char *) "-mono", (char *) "*pyraminx.mono", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nomono", (char *) "*pyraminx.mono", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-rv", (char *) "*pyraminx.reverseVideo", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-reverse", (char *) "*pyraminx.reverseVideo", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-norv", (char *) "*pyraminx.reverseVideo", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-noreverse", (char *) "*pyraminx.reverseVideo", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-fg", (char *) "*pyraminx.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-foreground", (char *) "*pyraminx.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-bg", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-background", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-bd", (char *) "*pyraminx.pieceBorder", XrmoptionSepArg, NULL},
	{(char *) "-border", (char *) "*pyraminx.pieceBorder", XrmoptionSepArg, NULL},
	{(char *) "-face0", (char *) "*pyraminx.faceColor0", XrmoptionSepArg, NULL},
	{(char *) "-face1", (char *) "*pyraminx.faceColor1", XrmoptionSepArg, NULL},
	{(char *) "-face2", (char *) "*pyraminx.faceColor2", XrmoptionSepArg, NULL},
	{(char *) "-face3", (char *) "*pyraminx.faceColor3", XrmoptionSepArg, NULL},
	{(char *) "-delay", (char *) "*pyraminx.delay", XrmoptionSepArg, NULL},
	{(char *) "-sound", (char *) "*pyraminx.sound", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nosound", (char *) "*pyraminx.sound", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-fn", (char *) "*pyraminx.font", XrmoptionSepArg, NULL},
	{(char *) "-font", (char *) "*pyraminx.font", XrmoptionSepArg, NULL},
	{(char *) "-size", (char *) "*pyraminx.size", XrmoptionSepArg, NULL},
	{(char *) "-sticky", (char *) "*pyraminx.sticky", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-mode", (char *) "*pyraminx.mode", XrmoptionSepArg, NULL},
	{(char *) "-both", (char *) "*pyraminx.mode", XrmoptionNoArg, (char *) "4"},
	{(char *) "-orient", (char *) "*pyraminx.orient", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-noorient", (char *) "*pyraminx.orient", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-practice", (char *) "*pyraminx.practice", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nopractice", (char *) "*pyraminx.practice", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-userName", (char *) "*pyraminx.userName", XrmoptionSepArg, NULL},
	{(char *) "-scoreFile", (char *) "*pyraminx.scoreFile", XrmoptionSepArg, NULL},
	{(char *) "-scores", (char *) "*pyraminx.scoreOnly", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-version", (char *) "*pyraminx.versionOnly", XrmoptionNoArg, (char *) "TRUE"}
};

#ifdef HAVE_MOTIF
static void
soundCallback(Widget w, XtPointer clientData,
		XmToggleButtonCallbackStruct *cbs)
{
	Boolean value = cbs->set;

	XtVaSetValues(puzzle,
		XtNsound, value, NULL);
}

#elif defined(HAVE_ATHENA)
static void
puzzlePracticeCancelCallback(Widget w, XtPointer clientData,
		XtPointer callData)
{
	XtPopdown(practiceDialog);
}

static void
createPracticeQuery(char *title, char *text)
{
	Widget dialog, okDialog, cancelDialog;

	practiceDialog = XtCreatePopupShell(title,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		dialogWidgetClass, practiceDialog,
		XtNlabel, text, NULL);
	okDialog = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(okDialog, XtNcallback,
		puzzlePracticeCallback, dialog);
	cancelDialog = XtVaCreateManagedWidget("Cancel",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(cancelDialog, XtNcallback,
		puzzlePracticeCancelCallback, dialog);
	XtRealizeWidget(practiceDialog);
	XSetWMProtocols(XtDisplay(topLevel),
		XtWindow(practiceDialog), &wmDeleteWindow, 1);
}

static void
puzzleRandomizeCancelCallback(Widget w, XtPointer clientData,
		XtPointer callData)
{
	XtPopdown(randomizeDialog);
}

static void
createRandomizeQuery(char *title, char *text)
{
	Widget dialog, okDialog, cancelDialog;

	randomizeDialog = XtCreatePopupShell(title,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		dialogWidgetClass, randomizeDialog,
		XtNlabel, text, NULL);
	okDialog = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(okDialog, XtNcallback,
		puzzleRandomizeCallback, dialog);
	cancelDialog = XtVaCreateManagedWidget("Cancel",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(cancelDialog, XtNcallback,
		puzzleRandomizeCancelCallback, dialog);
	XtRealizeWidget(randomizeDialog);
	XSetWMProtocols(XtDisplay(topLevel),
		XtWindow(randomizeDialog), &wmDeleteWindow, 1);
}
#endif

int
main(int argc, char **argv)
{
	int pixmapSize;
	XtAppContext appCon;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	Widget mainPanel, controlPanel, menuBar;
	Widget movesRowCol, changerRowCol;
	Widget playMenu;
#endif
#ifdef HAVE_MOTIF
	Widget pullDownMenu, widget;
	Widget menuBarPanel;
	Widget facetChangerRowCol, radioRowCol;
	Widget speedChangerRowCol, labelRowCol;
	Widget toggleRowCol, messageRowCol;
	XmString fileString, playString;
	XmString getString, writeString, quitString;
	XmString undoString, redoString, clearString;
#ifdef EXTRA
	XmString practiceString;
#endif
	XmString randomizeString, solveString;
#ifdef EXTRA
	XmString incrementString, decrementString;
	XmString orientString, stickyString;
	XmString speedString, slowString;
#endif
	XmString soundString;
	unsigned int i;
#elif defined(HAVE_ATHENA)
	XtActionsRec actions[] = {
		{(char *) "DeleteWindowProc", deleteWindowProc},
		{(char *) "ClosePopupPanel", (XtActionProc) closePopupPanel}
	};
	String fallbackResources[] = {
		(char *) "?.translations: #override <Message>WM_PROTOCOLS: DeleteWindowProc()",
		(char *) "?.TransientShell.translations: #override <Message>WM_PROTOCOLS: ClosePopupPanel()",
		NULL
	};
	Widget facetBox, modeBox, speedBox;
	Widget orientBox, stickyBox, practiceBox;
	Widget movesLabel, recordLabel;
	Widget modeRowCol, toggleRowCol;
	Widget w, fileMenu, helpMenu;
	Widget fileLabel, playLabel, helpLabel;
	int size, mode, delay;
	Boolean orient, sticky, practice;
	unsigned int i;
#endif

	progDsp = strrchr(argv[0], '/');
	if (progDsp != NULL)
		progDsp++;
	if (progDsp == NULL)
		progDsp = argv[0];
	topLevel = XtVaAppInitialize(NULL, "Pyraminx",
		options, XtNumber(options), &argc, argv,
#ifdef HAVE_ATHENA
		fallbackResources,
#else
		NULL,
#endif
		NULL);
	appCon = XtWidgetToApplicationContext(topLevel);
	if (argc != 1)
		usage(argv[0], synopsisHelp);
#ifdef HAVE_MOTIF
	menuBarPanel = XtVaCreateManagedWidget("menuBarPanel",
		xmPanedWindowWidgetClass, topLevel,
		XmNseparatorOn, False,
		XmNsashWidth, 1,
		XmNsashHeight, 1, NULL);
	fileString = XmStringCreateSimple((char *) "File");
	playString = XmStringCreateSimple((char *) "Play");
	menuBar = XmVaCreateSimpleMenuBar(menuBarPanel, (char *) "menuBar",
		XmVaCASCADEBUTTON, fileString, 'F',
		XmVaCASCADEBUTTON, playString, 'P', NULL);
	XmStringFree(fileString);
	XmStringFree(playString);
	getString = XmStringCreateSimple((char *) "Get");
	writeString = XmStringCreateSimple((char *) "Write");
	quitString = XmStringCreateSimple((char *) "Exit");
	(void) XmVaCreateSimplePulldownMenu(menuBar, (char *) "fileMenu",
		0, fileMenuCallback,
		XmVaPUSHBUTTON, getString, 'G', NULL, NULL,
		XmVaPUSHBUTTON, writeString, 'W', NULL, NULL,
		XmVaSEPARATOR,
		XmVaPUSHBUTTON, quitString, 'x', NULL, NULL, NULL);
	XmStringFree(getString);
	XmStringFree(writeString);
	XmStringFree(quitString);
	undoString = XmStringCreateSimple((char *) "Undo");
	redoString = XmStringCreateSimple((char *) "Redo");
	clearString = XmStringCreateSimple((char *) "Clear");
#ifdef EXTRA
	practiceString = XmStringCreateSimple((char *) "Practice");
#endif
	randomizeString = XmStringCreateSimple((char *) "Randomize");
	solveString = XmStringCreateSimple((char *) "Auto-solve");
#ifdef EXTRA
	incrementString = XmStringCreateSimple((char *) "Increment");
	decrementString = XmStringCreateSimple((char *) "Decrement");
	orientString = XmStringCreateSimple((char *) "Oriented");
	stickyString = XmStringCreateSimple((char *) "Sticky");
	speedString = XmStringCreateSimple((char *) "Speed >");
	slowString = XmStringCreateSimple((char *) "Slow <");
#endif
	soundString = XmStringCreateSimple((char *) "Sound @");
	playMenu = XmVaCreateSimplePulldownMenu(menuBar, (char *) "playMenu",
		1, playMenuCallback,
		XmVaPUSHBUTTON, undoString, 'U', NULL, NULL,
		XmVaPUSHBUTTON, redoString, 'R', NULL, NULL,
		XmVaPUSHBUTTON, clearString, 'C', NULL, NULL,
#ifdef EXTRA
		XmVaPUSHBUTTON, practiceString, 'P', NULL, NULL,
#endif
		XmVaPUSHBUTTON, randomizeString, 'z', NULL, NULL,
		XmVaPUSHBUTTON, solveString, 's', NULL, NULL,
#ifdef EXTRA
		XmVaPUSHBUTTON, incrementString, 'I', NULL, NULL,
		XmVaPUSHBUTTON, decrementString, 'D', NULL, NULL,
		XmVaPUSHBUTTON, orientString, 'O', NULL, NULL,
		XmVaPUSHBUTTON, stickyString, 'y', NULL, NULL,
		XmVaPUSHBUTTON, speedString, '>', NULL, NULL,
		XmVaPUSHBUTTON, slowString, '<', NULL, NULL,
#endif
		XmVaTOGGLEBUTTON, soundString, '@', NULL, NULL, NULL);
	XmStringFree(undoString);
	XmStringFree(redoString);
	XmStringFree(clearString);
#ifdef EXTRA
	XmStringFree(practiceString);
#endif
	XmStringFree(randomizeString);
	XmStringFree(solveString);
#ifdef EXTRA
	XmStringFree(incrementString);
	XmStringFree(decrementString);
	XmStringFree(orientString);
	XmStringFree(stickyString);
	XmStringFree(speedString);
	XmStringFree(slowString);
#endif
	XmStringFree(soundString);
	pullDownMenu = XmCreatePulldownMenu(menuBar,
		(char *) "helpPullDown", NULL, 0);
	widget = XtVaCreateManagedWidget("Help",
		xmCascadeButtonWidgetClass, menuBar,
		XmNsubMenuId, pullDownMenu,
		XmNmnemonic, 'H', NULL); /* mnemonic warning on Cygwin */
	XtVaSetValues(menuBar,
		XmNmenuHelpWidget, widget, NULL);
	widget = XtVaCreateManagedWidget("Description...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'D', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuCallback, (char *) 0);
	widget = XtVaCreateManagedWidget("Features...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'F', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuCallback, (char *) 1);
	widget = XtVaCreateManagedWidget("Synopsis...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'S', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuCallback, (char *) 2);
	widget = XtVaCreateManagedWidget("References...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'R', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuCallback, (char *) 3);
	widget = XtVaCreateManagedWidget("About...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'A', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuCallback, (char *) 4);
	XtManageChild(menuBar);
	solveDialog = createHelp(menuBar, "Solve", (char *) solveHelp);
	practiceDialog = createQuery(topLevel, "Practice Query",
		(char *) "Are you sure you want to toggle the practice mode?",
		(XtCallbackProc) puzzlePracticeCallback);
	randomizeDialog = createQuery(topLevel, "Randomize Query",
		(char *) "Are you sure you want to randomize?",
		(XtCallbackProc) puzzleRandomizeCallback);
	mainPanel = XtVaCreateManagedWidget("mainPanel",
		xmPanedWindowWidgetClass, menuBarPanel, NULL);
	controlPanel = XtVaCreateManagedWidget("controlPanel",
		xmPanedWindowWidgetClass, mainPanel,
		XmNseparatorOn, False,
		XmNsashWidth, 1,
		XmNsashHeight, 1, NULL);
	movesRowCol = XtVaCreateManagedWidget("movesRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
#ifdef MOUSEBITMAPS
	{
		/* Takes up valuable real estate and out of date. */
		Pixmap mouseLeftCursor, mouseRightCursor;
		Pixel fg, bg;

		(void) XtVaGetValues(movesRowCol,
			XmNforeground, &fg,
			XmNbackground, &bg, NULL);
		mouseLeftCursor = XCreatePixmapFromBitmapData(
			XtDisplay(movesRowCol),
			RootWindowOfScreen(XtScreen(movesRowCol)),
			(char *) mouse_left_bits,
			mouse_left_width, mouse_left_height, fg, bg,
			DefaultDepthOfScreen(XtScreen(movesRowCol)));
		mouseRightCursor = XCreatePixmapFromBitmapData(
			XtDisplay(movesRowCol),
			RootWindowOfScreen(XtScreen(movesRowCol)),
			(char *) mouse_right_bits,
			mouse_right_width, mouse_right_height, fg, bg,
			DefaultDepthOfScreen(XtScreen(movesRowCol)));
		(void) XtVaCreateManagedWidget("mouseLeftText",
			xmLabelGadgetClass, movesRowCol,
			XtVaTypedArg, XmNlabelString,
			XmRString, "Move", 5, NULL);
		(void) XtVaCreateManagedWidget("mouseLeft",
			xmLabelGadgetClass, movesRowCol,
			XmNlabelType, XmPIXMAP,
			XmNlabelPixmap, mouseLeftCursor, NULL);
		(void) XtVaCreateManagedWidget("mouseRightText",
			xmLabelGadgetClass, movesRowCol,
			XtVaTypedArg, XmNlabelString,
			XmRString, "Randomize", 10, NULL);
		(void) XtVaCreateManagedWidget("mouseRight",
			xmLabelGadgetClass, movesRowCol,
			XmNlabelType, XmPIXMAP,
			XmNlabelPixmap, mouseRightCursor, NULL);
	}
#endif
	(void) XtVaCreateManagedWidget("movesText",
		xmLabelGadgetClass, movesRowCol,
		XtVaTypedArg, XmNlabelString, XmRString, "Moves", 6, NULL);
	movesText = XtVaCreateManagedWidget("0",
		xmLabelWidgetClass, movesRowCol, NULL);
	(void) XtVaCreateManagedWidget("nullText",
		xmLabelGadgetClass, movesRowCol,
		XtVaTypedArg, XmNlabelString, XmRString, " ", 2, NULL);
	(void) XtVaCreateManagedWidget("recordText",
		xmLabelGadgetClass, movesRowCol,
		XtVaTypedArg, XmNlabelString, XmRString, "Record", 7, NULL);
	recordText = XtVaCreateManagedWidget("0",
		xmLabelWidgetClass, movesRowCol, NULL);
	changerRowCol = XtVaCreateManagedWidget("changerRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	facetChangerRowCol = XtVaCreateManagedWidget("facetChangerRowCol",
		xmRowColumnWidgetClass, changerRowCol,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(facetChangerRowCol, (char *) sizeLabel, NULL, 0));
	facetChanger = XtVaCreateManagedWidget("facetChanger",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, facetChangerRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_FACETS,
		XmNmaximumValue, MAX_FACETS,
		XmNposition, MIN_FACETS,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, facetChangerRowCol,
		XmNminimum, MIN_FACETS,
		XmNmaximum, MAX_FACETS,
		XmNvalue, MIN_FACETS,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Size", 5,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(facetChanger, XmNvalueChangedCallback,
		(XtCallbackProc) pieceChangeCallback, (XtPointer) NULL);
	speedChangerRowCol = XtVaCreateManagedWidget("speedChangerRowCol",
		xmRowColumnWidgetClass, changerRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(speedChangerRowCol, (char *) speedLabel, NULL, 0));
	speedChanger = XtVaCreateManagedWidget("speedChanger",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, speedChangerRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_SPEED,
		XmNmaximumValue, MAX_SPEED,
		XmNposition, MAX_SPEED - DEFAULT_DELAY,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, speedChangerRowCol,
		XmNminimum, MIN_SPEED,
		XmNmaximum, MAX_SPEED,
		XmNvalue, MAX_SPEED - DEFAULT_DELAY,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Animation Speed", 16,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(speedChanger, XmNvalueChangedCallback,
		(XtCallbackProc) speedChangeCallback, (XtPointer) NULL);
	labelRowCol = XtVaCreateManagedWidget("labelRowCol",
		xmRowColumnWidgetClass, controlPanel, NULL);
	(void) XtVaCreateManagedWidget("PeriodText",
		xmLabelWidgetClass, labelRowCol,
		XtVaTypedArg, XmNlabelString, XmRString, "Period:", 8, NULL);
	radioRowCol = XtVaCreateManagedWidget("radioRowCol",
		xmRowColumnWidgetClass, labelRowCol,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		XmNradioBehavior, True, NULL);
	for (i = 0; i < XtNumber(modeString); i++) {
		modes[i] = XtVaCreateManagedWidget(modeString[i],
			xmToggleButtonGadgetClass, radioRowCol,
			XmNradioBehavior, True, NULL);
		XtAddCallback(modes[i], XmNvalueChangedCallback,
			(XtCallbackProc) modeCallback,
			(XtPointer) ((size_t) i));
	}
	toggleRowCol = XtVaCreateManagedWidget("toggleRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	orientToggle = XtVaCreateManagedWidget("Oriented",
		xmToggleButtonWidgetClass, toggleRowCol,
		XmNset, DEFAULT_ORIENT, NULL);
	XtAddCallback(orientToggle, XmNvalueChangedCallback,
		(XtCallbackProc) orientCallback, (XtPointer) NULL);
	stickyToggle = XtVaCreateManagedWidget("Sticky",
		xmToggleButtonWidgetClass, toggleRowCol,
		XmNset, DEFAULT_STICKY, NULL);
	XtAddCallback(stickyToggle, XmNvalueChangedCallback,
		(XtCallbackProc) stickyCallback, (XtPointer) NULL);
	practiceToggle = XtVaCreateManagedWidget("Practice",
		xmToggleButtonWidgetClass, toggleRowCol,
		XmNset, DEFAULT_PRACTICE, NULL);
	XtAddCallback(practiceToggle, XmNvalueChangedCallback,
		(XtCallbackProc) practiceCallback, (XtPointer) NULL);
	messageRowCol = XtVaCreateManagedWidget("messageRowCol",
		xmRowColumnWidgetClass, controlPanel, NULL);
	message = XtVaCreateManagedWidget("Play Pyraminx! (use mouse and keypad)",
		xmLabelWidgetClass, messageRowCol, NULL);
	puzzle = XtVaCreateManagedWidget("pyraminx",
		pyraminxWidgetClass, mainPanel, NULL);
#elif defined(HAVE_ATHENA)
	XtAppAddActions(appCon, actions, XtNumber(actions));
	createPracticeQuery((char *) "Practice Query",
		(char *) "Are you sure you want to toggle the practice mode?");
	createRandomizeQuery((char *) "Randomize Query",
		(char *) "Are you sure you want to randomize?");
	createHelp(topLevel, &solveDialog, (char *) "Auto-solve",
		(char *) solveHelp, (XtCallbackProc) closePopupPanel2);
	createHelp(topLevel, &descriptionDialog, (char *) "Description",
		(char *) descriptionHelp, (XtCallbackProc) closePopupPanel2);
	createScrollHelp(topLevel, &featuresDialog, (char *) "Features",
		(char *) featuresHelp, (XtCallbackProc) closePopupPanel2);
	createHelp(topLevel, &synopsisDialog, (char *) "Synopsis",
		(char *) synopsisHelp, (XtCallbackProc) closePopupPanel2);
	createHelp(topLevel, &referencesDialog, (char *) "References",
		(char *) referencesHelp, (XtCallbackProc) closePopupPanel2);
	createHelp(topLevel, &aboutDialog, (char *) "About",
		(char *) aboutHelp, (XtCallbackProc) closePopupPanel2);
	mainPanel = XtVaCreateManagedWidget("form",
		panedWidgetClass, topLevel, NULL);
	menuBar = XtVaCreateManagedWidget("MenuBar",
		formWidgetClass, mainPanel,
		XtNborderWidth, 1, NULL);
	createMenu(menuBar, &fileLabel, &fileMenu, NULL,
		fileTypes, "File", numFileTypes,
		0, False, fileMenuCallback);
	playLabel = XtVaCreateManagedWidget("Play",
		menuButtonWidgetClass, menuBar,
		XtNborderWidth, 0,
		XtNfromHoriz, fileLabel, NULL);
	playMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, playLabel,
		XtNrightMargin, 8, NULL); /* for check */
	for (i = 0; i < numPlayTypes; i++) {
		w = XtVaCreateManagedWidget(playTypes[i],
			smeBSBObjectClass, playMenu, NULL);
		if (i == numPlayTypes - 1)
			soundMenuItem = w;
		XtAddCallback(w,
			XtNcallback, (XtCallbackProc) playMenuCallback,
			(XtPointer) (size_t) i);
	}
	createMenu(menuBar, &helpLabel, &helpMenu, playLabel,
		helpTypes, "Help", numHelpTypes,
		0, False, helpMenuCallback);
	controlPanel = XtVaCreateManagedWidget("controlPanel",
		formWidgetClass, mainPanel,
		XtNborderWidth, 0, NULL);
	movesRowCol = XtVaCreateManagedWidget("movesRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0, NULL);
	movesLabel = XtVaCreateManagedWidget("Moves",
		labelWidgetClass, movesRowCol,
		XtNborderWidth, 0, NULL);
	movesText = XtVaCreateManagedWidget("0",
		labelWidgetClass, movesRowCol,
		XtNwidth, 32,
		XtNfromHoriz, movesLabel, NULL);
	recordLabel = XtVaCreateManagedWidget("Record",
		labelWidgetClass, movesRowCol,
		XtNborderWidth, 0,
		XtNfromHoriz, movesText, NULL);
	recordText = XtVaCreateManagedWidget("0",
		labelWidgetClass, movesRowCol,
		XtNwidth, 108,
		XtNfromHoriz, recordLabel, NULL);
	changerRowCol = XtVaCreateManagedWidget("changerRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, movesRowCol, NULL);
	facetBox = XtVaCreateManagedWidget("facetBox",
		boxWidgetClass, changerRowCol,
		XtNorientation, XtorientHorizontal,
		XtNborderWidth, 0, NULL);
	speedBox = XtVaCreateManagedWidget("speedBox",
		boxWidgetClass, changerRowCol,
		XtNorientation, XtorientHorizontal,
		XtNborderWidth, 0,
		XtNfromHoriz, facetBox, NULL);
	modeRowCol = XtVaCreateManagedWidget("modeRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, changerRowCol, NULL);
	modeBox = XtVaCreateManagedWidget("modeBox",
		boxWidgetClass, modeRowCol,
		XtNorientation, XtorientHorizontal,
		XtNborderWidth, 0, NULL);
	stickyBox = XtVaCreateManagedWidget("Sticky",
		boxWidgetClass, modeRowCol,
		XtNorientation, XtorientHorizontal,
		XtNstate, DEFAULT_STICKY,
		XtNborderWidth, 0,
		XtNfromHoriz, modeBox, NULL);
	toggleRowCol = XtVaCreateManagedWidget("toggleRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, modeRowCol, NULL);
	orientBox = XtVaCreateManagedWidget("Oriented",
		boxWidgetClass, toggleRowCol,
		XtNorientation, XtorientHorizontal,
		XtNstate, DEFAULT_ORIENT,
		XtNborderWidth, 0, NULL);
	practiceBox = XtVaCreateManagedWidget("Practice",
		boxWidgetClass, toggleRowCol,
		XtNorientation, XtorientHorizontal,
		XtNstate, DEFAULT_PRACTICE,
		XtNborderWidth, 0,
		XtNfromHoriz, orientBox, NULL);
	message = XtVaCreateManagedWidget("Welcome",
		labelWidgetClass, controlPanel,
		XtNjustify,  XtJustifyLeft,
		XtNborderWidth, 0,
		XtNwidth, 180,
		XtNfromVert, toggleRowCol, NULL);
	puzzle = XtVaCreateManagedWidget("oct",
		pyraminxWidgetClass, mainPanel,
		XtNfromVert, controlPanel, NULL);
	XtVaGetValues(puzzle,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &practice,
		XtNdelay, &delay, NULL);
	createPopupMenu(modeBox, &modeLabel, &modeMenu, modeString,
		"Period:", 0, mode - PERIOD2,
		sizeof(modeString) / sizeof(*modeString),
		(XtCallbackProc) modeCallback);
#ifdef USE_SPIN
	createSpinner(facetBox, &facetSliderLabel,
		sizeLabel, 0, size, MIN_FACETS, MAX_FACETS, False,
		pieceUpCallback, pieceDownCallback);
	createSpinner(speedBox, &speedSliderLabel,
		speedLabel, 0, getSpeed(delay), MIN_SPEED, MAX_SPEED, True,
		speedUpCallback, speedDownCallback);
#else
	createSlider(facetBox, &facetSliderLabel, &facetSlider,
		sizeLabel, 0, size, MIN_FACETS, MAX_FACETS, False,
		SCROLL_SIZE, pieceChangeCallback, pieceChangeCallback);
	createSlider(speedBox, &speedSliderLabel, &speedSlider,
		speedLabel, 0, getSpeed(delay), MIN_SPEED, MAX_SPEED, True,
		SCROLL_SIZE, speedChangeCallback, speedChangeCallback);
#endif
	createToggle(orientBox, &orientToggle, "Oriented:", 0,
		orient, (XtCallbackProc) orientCallback);
	createToggle(stickyBox, &stickyToggle, "Sticky:", 0,
		sticky, (XtCallbackProc) stickyCallback);
	createToggle(practiceBox, &practiceToggle, "Practice:", 0,
		practice, (XtCallbackProc) practiceCallback);
	XtRealizeWidget(topLevel);
	if (wmDeleteWindow == None)
		wmDeleteWindow = XInternAtom(XtDisplay(topLevel),
			"WM_DELETE_WINDOW", FALSE);
	XSetWMProtocols(XtDisplay(topLevel), XtWindow(topLevel),
		&wmDeleteWindow, 1);
#else
	puzzle = XtVaCreateManagedWidget("pyraminx",
		pyraminxWidgetClass, topLevel, NULL);
#endif
	XtVaGetValues(puzzle,
		XtNpixmapSize, &pixmapSize, NULL);
#ifdef HAVE_XPM
	{
		XpmAttributes xpmAttributes;
		XpmColorSymbol transparentColor[1] = {{NULL,
			(char *) "none", 0 }};
		Pixel bg;

		xpmAttributes.valuemask = XpmColorSymbols | XpmCloseness;
		xpmAttributes.colorsymbols = transparentColor;
		xpmAttributes.numsymbols = 1;
		xpmAttributes.closeness = 40000;
		XtVaGetValues(topLevel,
			XtNbackground, &bg, NULL);
		transparentColor[0].pixel = bg;
		(void) XpmCreatePixmapFromData(XtDisplay(topLevel),
			RootWindowOfScreen(XtScreen(topLevel)),
			RESIZE_XPM(pixmapSize), &pixmap, NULL,
			&xpmAttributes);
	}
	if (pixmap == (Pixmap) NULL)
#endif
		pixmap = XCreateBitmapFromData(XtDisplay(topLevel),
			RootWindowOfScreen(XtScreen(topLevel)),
			DEFINE_XBM);
	XtVaSetValues(topLevel,
#ifdef HAVE_MOTIF
		XmNkeyboardFocusPolicy, XmPOINTER, /* not XmEXPLICIT */
#else
		XtNinput, True,
#endif
		XtNiconPixmap, pixmap, NULL);
	XtAddCallback(puzzle, XtNselectCallback,
		(XtCallbackProc) puzzleCallback, (XtPointer) NULL);
#ifdef HAVE_MOTIF
	updateToggle(playMenu, &soundMenuItem,
		False,
#ifdef EXTRA
		12,
#else
		5,
#endif
		(XtCallbackProc) soundCallback);
	descriptionDialog = createHelp(menuBar, (char *) "Description",
		(char *) descriptionHelp);
	featuresDialog = createScrollHelp(menuBar, (char *) "Features",
		(char *) featuresHelp, pixmap);
	synopsisDialog = createHelp(menuBar, (char *) "Synopsis",
		(char *) synopsisHelp);
	referencesDialog = createHelp(menuBar, (char *) "References",
		(char *) referencesHelp);
	aboutDialog = createHelp(menuBar, (char *) "About",
		(char *) aboutHelp);
#endif
	initialize(puzzle);
	XtRealizeWidget(topLevel);
	XGrabButton(XtDisplay(puzzle), (unsigned int) AnyButton, AnyModifier,
		XtWindow(puzzle), TRUE, (unsigned int) (ButtonPressMask |
		ButtonMotionMask | ButtonReleaseMask),
		GrabModeAsync, GrabModeAsync, XtWindow(puzzle),
		XCreateFontCursor(XtDisplay(puzzle), XC_hand2));
	XtAppMainLoop(appCon);

#ifdef VMS
	return 1;
#else
	return 0;
#endif
}
#endif
