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

/* 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"
"[-{border|bd} {color}] [-face{0|1|2|3} {color}]\n"
"[-frame {color}] [-delay msecs] [-[no]sound]\n"
"[-moveSound {filename}] [-{font|fn} {fontname}]\n"
"[-view {int}] [-{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 "
"where size < 4 in Period 2 mode (or sticky mode is set)\n"
"and size < 5 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 \"V\" or \"v\" keys to change the view of the puzzle.\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 (or sticky "
"mode set) where period = 2 or size < 5 where period = 3."
};

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

static PyraminxRec widget;

#ifndef SCOREPATH
#ifdef UNIXDELIM
#define SCOREPATH "C:/ProgramData/wpuzzles"
#else
#define SCOREPATH "C:\\ProgramData\\wpuzzles"
#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"
#include "Pyraminx2d.h"
#ifdef HAVE_OPENGL
#include "PyraminxGL.h"
#endif
#ifdef HAVE_XPM
#include <X11/xpm.h>
#ifdef CONSTPIXMAPS
#include "icons/16x16/pyraminx.xpm"
#include "icons/22x22/pyraminx.xpm"
#include "icons/24x24/pyraminx.xpm"
#include "icons/32x32/pyraminx.xpm"
#include "icons/48x48/pyraminx.xpm"
#include "icons/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
#ifdef HAVE_EDITRES
#ifdef __VMS
#include <Xmu/Editres.h>
#else
#include <X11/Xmu/Editres.h>
#endif
#endif
#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 = DEFAULT_FACETS;

#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 *modeLabel = "Period:";
static const char *speedLabel = "Animation Speed:";
#define PRINT_MESSAGE(b) printState(message, b)
#define MIN_SPEED 1
#define MAX_SPEED 50
static const char *modeStrings[] =
{
	"Period 2", "Period 3", "Both"
};
#elif !defined(WINVER)
#define PRINT_MESSAGE(b) XtWarning(b)
#endif
#ifdef HAVE_MOTIF
static Widget modeRadio[MAX_MODES];
static Widget facetChanger, speedChanger;
#elif defined(HAVE_ATHENA)
#ifdef USE_POPUP
static Widget modeNameLabel;
#else
static Widget modeRadio[MAX_MODES];
#endif
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 @"
#ifdef HAVE_OPENGL
	, "View"
#endif
};
#define numPlayTypes (sizeof(playTypes)/sizeof(playTypes[0]))
static const char *helpTypes[] =
{
	"Description...",
	"Features...",
	"Synopsis...",
	"References...",
	"About..."
};
#define numHelpTypes (sizeof(helpTypes)/sizeof(helpTypes[0]))
#else
#if !defined(WINVER) && defined(HAVE_OPENGL)
static Widget shell;
#endif
#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, puzzle2d;
#ifdef HAVE_OPENGL
static Widget puzzle3d;
#endif
static char userNameDsp[USER_NAME_LENGTH] = "";
#define WINDOW_HEIGHT 300

#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  SIZE 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
}

#ifndef WINVER
/* There is probably a better way to assure that they are the same
 * but I do not know it off hand. */
static void
makeEquivalent(String *userName, String *scoreFile, int *size, int *mode,
		Boolean *sticky, Boolean *orient, Boolean *practice, int *delay)
{
	Boolean mono, reverse;
	Boolean scoreOnly, versionOnly;
	Pixel foreground, background, pieceBorder;
	String faceColor[MAX_FACES];

	XtVaGetValues(puzzle2d,
		XtNuserName, userName,
		XtNscoreFile, scoreFile,
		XtNsize, size,
		XtNmode, mode,
		XtNsticky, sticky,
		XtNorient, orient,
		XtNpractice, practice,
		XtNmono, &mono,
		XtNreverseVideo, &reverse,
		XtNdelay, delay,
		XtNforeground, &foreground,
		XtNbackground, &background,
		XtNpieceBorder, &pieceBorder,
		XtNfaceColor0, &(faceColor[0]),
		XtNfaceColor1, &(faceColor[1]),
		XtNfaceColor2, &(faceColor[2]),
		XtNfaceColor3, &(faceColor[3]),
		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);
	}
	if (*sticky && *size != 4) {
		*size = 4;
		XtVaSetValues(puzzle2d,
			XtNsize, size, NULL);
	}
	XtVaSetValues(puzzle2d,
		XtNdirection, IGNORE_DIR,
		XtNstart, False, NULL);
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNuserName, *userName,
		XtNsize, *size,
		XtNmode, *mode,
		XtNsticky, *sticky,
		XtNorient, *orient,
		XtNpractice, *practice,
		XtNmono, mono,
		XtNreverseVideo, reverse,
		XtNdelay, *delay,
		XtNdirection, IGNORE_DIR,
		XtNstart, False,
		XtNforeground, foreground,
		XtNbackground, background,
		XtNpieceBorder, pieceBorder,
		XtNfaceColor0, faceColor[0],
		XtNfaceColor1, faceColor[1],
		XtNfaceColor2, faceColor[2],
		XtNfaceColor3, faceColor[3], NULL);
#endif
}
#endif

static void
printStatus(char *msg, int nMoves
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		, int dim, 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 dimDsp[3];
	char ss[10], mb[10];
#ifdef HAVE_OPENGL
	if (dim == 4)
		(void) strncpy(dimDsp, "GL", 3);
	else
		(void) strncpy(dimDsp, "2d", 3);
#else
	dimDsp[0] = '\0';
#endif
	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: %s@ (%d/%s) - %s",
		progDsp, dimDsp, mb, ss, nMoves, recordDsp, msg);
#else
	(void) sprintf(titleDsp,
		"%s%s.%s: %s@ (%d/%s) - %s",
		progDsp, dimDsp, mb, ss, nMoves, recordDsp, msg);
#endif
#ifdef WINVER
	SetWindowText(widget.core.hWnd, (LPSTR) titleDsp);
#else
#ifdef HAVE_OPENGL
	XtVaSetValues(XtParent((dim == 2) ? puzzle2d: puzzle3d),
		XtNtitle, titleDsp, NULL);
#else
	XtVaSetValues(XtParent(puzzle2d),
		XtNtitle, titleDsp, NULL);
#endif
#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(puzzle2d,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &old,
		XtNsticky, &sticky,
		XtNpractice, &practice, NULL);
	if (old == orient)
		return;
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNorient, orient, NULL);
#endif
	XtVaSetValues(puzzle2d,
		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(puzzle2d,
		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(puzzle2d,
			XtNsize, size, NULL);
#ifdef HAVE_OPENGL
		XtVaSetValues(puzzle3d,
			XtNsize, size, NULL);
#endif
	}
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNsticky, sticky, NULL);
#endif
	XtVaSetValues(puzzle2d,
		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(puzzle2d,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &old, NULL);
	if (old == practice)
		return;
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNpractice, practice,
		XtNstart, False, NULL);
#endif
	XtVaSetValues(puzzle2d,
		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(puzzle2d,
		XtNsize, &size, NULL);
	size++;
	setScale(facetSlider, facetSliderLabel, size,
		MIN_FACETS, MAX_FACETS, False);
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNsize, size, NULL);
#endif
	XtVaSetValues(puzzle2d,
		XtNsize, size, NULL);
}

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

	XtVaGetValues(puzzle2d,
		XtNsize, &size, NULL);
	size--;
	if (size < 1) {
		return;
	}
	setScale(facetSlider, facetSliderLabel, size,
		MIN_FACETS, MAX_FACETS, False);
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNsize, size, NULL);
#endif
	XtVaSetValues(puzzle2d,
		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(puzzle2d,
		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;
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNsize, size, NULL);
#endif
	XtVaSetValues(puzzle2d,
		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(puzzle2d,
		XtNdelay, &delay, NULL);
	speed = getSpeed(delay);
	speed++;
	if (speed > MAX_SPEED) {
		return;
	}
	setScale(speedSlider, speedSliderLabel, speed,
		MIN_SPEED, MAX_SPEED, True);
	delay = getDelay(speed);
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNdelay, delay, NULL);
#endif
	XtVaSetValues(puzzle2d,
		XtNdelay, delay, NULL);
}

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

	XtVaGetValues(puzzle2d,
		XtNdelay, &delay, NULL);
	speed = getSpeed(delay);
	speed--;
	if (speed < MIN_SPEED) {
		return;
	}
	setScale(speedSlider, speedSliderLabel, speed,
		MIN_SPEED, MAX_SPEED, True);
	delay = getDelay(speed);
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNdelay, delay, NULL);
#endif
	XtVaSetValues(puzzle2d,
		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(puzzle2d,
		XtNdelay, &old, NULL);
	if (old == delay)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, speed, MIN_SPEED, MAX_SPEED, True);
#endif
#ifdef HAVE_OPENGL
	XtVaSetValues(puzzle3d,
		XtNdelay, delay, NULL);
#endif
	XtVaSetValues(puzzle2d,
		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, state = True;
	int size, mode, old;
#ifdef HAVE_MOTIF
	if (!cbs->set)
		return;
#elif defined(HAVE_ATHENA)
	int position = (size_t) clientData;
#endif

	XtVaGetValues(puzzle2d,
		XtNsize, &size,
		XtNmode, &old,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &practice, NULL);
	mode = position + PERIOD2;
	if (old == mode)
		return;
#ifdef HAVE_ATHENA
#ifdef USE_POPUP
	XtVaSetValues(modeNameLabel,
		XtNlabel, modeStrings[position], NULL);
#else
	XtVaGetValues(modeRadio[position],
		XtNstate, &state, NULL);
#endif
#endif
	if (state) {
#ifdef HAVE_OPENGL
		XtVaSetValues(puzzle3d,
			XtNmode, mode, NULL);
#endif
		XtVaSetValues(puzzle2d,
			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(puzzle2d,
			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(puzzle2d,
			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(puzzle2d,
		XtNmenu, value, NULL);
}

static void
setSoundCheck(Widget w)
{
	Boolean sound;
	XtVaGetValues(puzzle2d,
		XtNsound, &sound, NULL);
#ifdef HAVE_MOTIF
	setToggle(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(puzzle2d,
		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
	int dim;

	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;
	dim = w->pyraminx.dim;
	SET_STARTED(w, False);
	if (strcmp(scoreFile, "")) {
		(void) strncpy(scoreFileName, scoreFile, FILE_NAME_LENGTH - 1);
		scoreFileName[FILE_NAME_LENGTH - 1] = '\0';
	}
#else
	int delay, oldSize;
	XtVaGetValues(w,
		XtNsize, &oldSize, NULL);
	makeEquivalent(&userName, &scoreFile, &size, &mode, &sticky,
		&orient, &practice, &delay);
#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
	setToggle(modeRadio[mode - PERIOD2], True, True);
	setToggle(orientToggle, orient, False);
	setToggle(stickyToggle, sticky, False);
	setToggle(practiceToggle, practice, False);
	{
		int speed;
		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 speed;
		XtVaGetValues(w,
			XtNdelay, &delay, NULL);
		speed = getSpeed(delay);
		setScale(speedSlider, speedSliderLabel, speed,
			MIN_SPEED, MAX_SPEED, True);
	}
	setSoundCheck(soundMenuItem);
#endif
#endif
	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;
#ifdef WINVER
	printStatus(messageDsp, movesDsp, dim, mode, size, sticky);
#else
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	printStatus(messageDsp, movesDsp);
#else
	printStatus(messageDsp, movesDsp, 2, mode, size, sticky);
#ifdef HAVE_OPENGL
	printStatus(messageDsp, movesDsp, 4, mode, size, sticky);
#endif
#endif
#endif
}

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

#ifdef HAVE_OPENGL
	if (w == puzzle2d) {
		otherw = puzzle3d;
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		dim = 2;
		otherdim = 4;
#endif
	} else if (w == puzzle3d) {
		otherw = puzzle2d;
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		otherdim = 2;
		dim = 4;
#endif
	}
#endif
#endif
	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;*/
	dim = w->pyraminx.dim;
#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)));
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA) && defined(HAVE_OPENGL)
		(void) XIconifyWindow(XtDisplay(shell),
			XtWindow(shell),
			XScreenNumberOfScreen(XtScreen(shell)));
#endif
#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(puzzle2d,
			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(puzzle2d,
			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;
#ifndef WINVER
		if (otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNdirection, RESTORE_DIR, NULL);
		XtVaSetValues(w,
			XtNdirection, RESTORE_DIR, NULL);
#endif
		break;
	case ACTION_CLEAR:
		movesDsp = 0;
		randomized = False;
#ifndef WINVER
		if (otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNdirection, CLEAR_DIR, NULL);
		XtVaSetValues(w,
			XtNdirection, CLEAR_DIR, NULL);
#endif
		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;
#ifndef WINVER
	case ACTION_MOVED:
		if (!callData->control)
			movesDsp++;
		if (callData->style != -1 && otherw != (Widget) NULL) {
#ifdef DEBUG
			(void) printf("MOVED: face %d, position %d, direction %d, style %d, control %d\n",
				callData->face, callData->position, callData->direction,
				callData->style, callData->control);
#endif
			XtVaSetValues(otherw,
				XtNface, callData->face,
				XtNpos, callData->position,
				XtNdirection, callData->direction,
				XtNstyle, callData->style,
				XtNcontrol, callData->control,
				XtNfast, callData->fast,
				XtNstart, True, NULL);
		}
		SET_STARTED(w, True);
		break;
#endif
	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);
#ifndef WINVER
		if (otherw != (Widget) NULL)
			SET_STARTED(otherw, False);
#endif
		randomized = False;
		break;
	case ACTION_COMPUTED:
		SET_STARTED(w, False);
#ifndef WINVER
		if (otherw != (Widget) NULL)
			SET_STARTED(otherw, False);
#endif
		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);
		if (otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNpractice, practice,
				XtNstart, False, NULL);
#ifdef HAVE_MOTIF
		setToggle(practiceToggle, practice, False);
#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);
		if (otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNpractice, False,
				XtNstart, False, NULL);
#endif
		break;
#ifdef HAVE_OPENGL
	case ACTION_VIEW:
		{
			int view;

#ifdef WINVER
			view = w->pyraminx.view;
#else
			XtVaGetValues(puzzle3d,
				XtNview, &view, NULL);
#endif
			view = (view + 1) % (2 * MAX_VIEWS);
#ifdef WINVER
			w->pyraminx.view = view;
#else
			XtVaSetValues(puzzle3d,
				XtNview, view, NULL);
#endif
		}
		break;
#endif
	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);
			if (otherw != (Widget) NULL)
				XtVaSetValues(otherw,
					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);
			if (otherw != (Widget) NULL)
				XtVaSetValues(otherw,
					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);
		if (otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNorient, orient, NULL);
#ifdef HAVE_MOTIF
		setToggle(orientToggle, orient, False);
#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);
		if (otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNmode, mode, NULL);
#ifdef HAVE_MOTIF
		setToggle(modeRadio[mode - PERIOD2], True, True);
#elif defined(HAVE_ATHENA)
#ifdef USE_POPUP
		max = findMaxLength((char **) modeStrings,
			sizeof(modeStrings) / sizeof(*modeStrings));
		createBlank(&defaultString, max, (char *) modeStrings[mode - PERIOD2], 0);
		XtVaSetValues(modeNameLabel,
			XtNlabel, defaultString, NULL);
		free(defaultString);
#else
		XtVaSetValues(modeRadio[mode - PERIOD2],
			XtNstate, True, NULL);
#endif
#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);
		if (otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNsticky, sticky,
				XtNsize, size, NULL);
#ifdef HAVE_MOTIF
		setToggle(stickyToggle, sticky, False);
#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;
#ifndef WINVER
	case ACTION_UNDO:
		movesDsp--;
		if (callData->style != -1 && otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNstart, True,
				XtNface, callData->face,
				XtNpos, callData->position,
				XtNdirection, callData->direction,
				XtNstyle, callData->style,
				XtNcontrol, callData->control,
				XtNfast, callData->fast, NULL);
		SET_STARTED(w, True);
		break;
	case ACTION_REDO:
		movesDsp++;
		if (callData->style != -1 && otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNstart, True,
				XtNface, callData->face,
				XtNpos, callData->position,
				XtNdirection, callData->direction,
				XtNstyle, callData->style,
				XtNcontrol, callData->control,
				XtNfast, callData->fast, NULL);
		SET_STARTED(w, True);
		break;
#endif
#if defined(WINVER) && defined(HAVE_OPENGL)
	case ACTION_DIM:
		dim++;
		if (dim == 3)
			dim = 4;
		else if (dim > 4)
			dim = 2;
		w->pyraminx.dim = dim;
		break;
#endif
#ifndef WINVER
	case ACTION_CHEAT:
		XtVaSetValues(w,
			XtNcheat, cheat, NULL);
		if (otherw != (Widget) NULL)
			XtVaSetValues(otherw,
				XtNcheat, cheat, NULL);
		break;
#endif
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	case ACTION_SPEED:
		{
			int oldDelay, delay, speed;

			XtVaGetValues(puzzle2d,
				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
	}
#ifdef WINVER
	printStatus(messageDsp, movesDsp, dim, mode, size, sticky);
#else
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	printStatus(messageDsp, movesDsp);
#else
	printStatus(messageDsp, movesDsp, dim, mode, size, sticky);
#ifdef HAVE_OPENGL
	printStatus(messageDsp, movesDsp, otherdim, mode, size, sticky);
#endif
#endif
#endif
}

#ifdef WINVER
void
setPuzzleMove(PyraminxWidget w, int reason, int face, int position,
		int direction, int style, int control, int fast)
{
	int mode, size;
	Boolean sticky;
	/* Boolean orient, practice, cheat; */
	int dim;

	messageDsp[0] = '\0';
	mode = w->pyraminx.mode;
	size = w->pyraminx.size;
	sticky = w->pyraminx.sticky;
#if 0
	orient = w->pyraminx.orient;
	practice = w->pyraminx.practice;
	cheat = w->pyraminx.cheat;
#endif
	dim = w->pyraminx.dim;
	switch (reason) {
	case ACTION_MOVED:
		movesDsp++;
		SET_STARTED(w, True);
		break;
	case ACTION_CONTROL:
		break;
	case ACTION_UNDO:
		movesDsp--;
		SET_STARTED(w, True);
		break;
	case ACTION_REDO:
		movesDsp++;
		SET_STARTED(w, True);
		break;
	}
	printStatus(messageDsp, movesDsp, dim, mode, size, sticky);
}

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:
			clearPuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			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;
#ifdef HAVE_OPENGL
		case ACTION_DIM:
			(void) dimPuzzle(&widget);
			resizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
#endif
		case ACTION_VIEW:
			(void) viewPuzzle(&widget);
			resizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_SHIFT_TOP:
		case ACTION_SHIFT_TR:
		case ACTION_SHIFT_RIGHT:
		case ACTION_SHIFT_BR:
		case ACTION_SHIFT_BOTTOM:
		case ACTION_SHIFT_BL:
		case ACTION_SHIFT_LEFT:
		case ACTION_SHIFT_TL:
		case ACTION_SHIFT_CW:
		case ACTION_SHIFT_CCW:
		case ACTION_TOP:
		case ACTION_TR:
		case ACTION_RIGHT:
		case ACTION_BR:
		case ACTION_BOTTOM:
		case ACTION_BL:
		case ACTION_LEFT:
		case ACTION_TL:
		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_BR:
		case ACTION_SHIFT_CONTROL_BOTTOM:
		case ACTION_SHIFT_CONTROL_BL:
		case ACTION_SHIFT_CONTROL_LEFT:
		case ACTION_SHIFT_CONTROL_TL:
		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_BR:
		case ACTION_CONTROL_BOTTOM:
		case ACTION_CONTROL_BL:
		case ACTION_CONTROL_LEFT:
		case ACTION_CONTROL_TL:
		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 *) "-frame", (char *) "*pyraminx.frameColor", 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 *) "-view", (char *) "*pyraminx.view", XrmoptionSepArg, NULL},
	{(char *) "-size", (char *) "*pyraminx.size", XrmoptionSepArg, NULL},
	{(char *) "-sticky", (char *) "*pyraminx.sticky", XrmoptionNoArg, (char *) "TRUE"},
	{(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(puzzle2d,
		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 toggleRowCol;
	Widget playMenu;
#endif
#ifdef HAVE_MOTIF
	Widget pullDownMenu, widget;
	Widget menuBarPanel;
	Widget facetChangerRowCol, speedChangerRowCol;
	Widget labelRowCol;
	Widget 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;
#ifdef HAVE_OPENGL
	XmString viewString;
#endif
#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;
#ifndef USE_SPIN
	Widget speedChangerRowCol;
#endif
	Widget orientBox, stickyBox, practiceBox;
	Widget movesLabel, recordLabel;
	Widget modeRowCol;
	Widget w;
	Widget fileLabel, playLabel, helpLabel;
	int size, mode, delay;
	Boolean orient, sticky, practice;
	unsigned int i;
#elif defined(HAVE_OPENGL)
	char titleDsp[TITLE_FILE_LENGTH];
#endif

#ifdef __VMS
	int n;
	progDsp = strrchr(argv[0], ']');
	for (n = 0; progDsp[n] != '\0' && progDsp[n] != '.'; n++);
	progDsp[n] = '\0';
#else
	progDsp = strrchr(argv[0], '/');
#endif
	if (progDsp != NULL)
		progDsp++;
	if (progDsp == NULL)
		progDsp = argv[0];
	topLevel = XtVaAppInitialize(NULL, "XPyraminx",
		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 @");
#ifdef HAVE_OPENGL
	viewString = XmStringCreateSimple((char *) "View");
#endif
	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,
#ifdef HAVE_OPENGL
		XmVaPUSHBUTTON, viewString, 'V', NULL, NULL,
#endif
		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);
#ifdef HAVE_OPENGL
	XmStringFree(viewString);
#endif
	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);
#ifdef USE_SPIN
	createSpinner(facetChangerRowCol, &facetChanger,
		(char *) sizeLabel, DEFAULT_FACETS,
		MIN_FACETS, MAX_FACETS, 2,
		(XtCallbackProc) pieceChangeCallback);
	speedChangerRowCol = XtVaCreateManagedWidget("speedChangerRowCol",
		xmRowColumnWidgetClass, changerRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	createSpinner(speedChangerRowCol, &speedChanger,
		(char *) speedLabel, MAX_SPEED - DEFAULT_DELAY,
		MIN_SPEED, MAX_SPEED, 2,
		(XtCallbackProc) speedChangeCallback);
#else
	createSlider(facetChangerRowCol, &facetChanger,
		(char *) sizeLabel, DEFAULT_FACETS,
		MIN_FACETS, MAX_FACETS, 2, SCROLL_SIZE,
		(XtCallbackProc) pieceChangeCallback);
	speedChangerRowCol = XtVaCreateManagedWidget("speedChangerRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	createSlider(speedChangerRowCol, &speedChanger,
		(char *) speedLabel, MAX_SPEED - DEFAULT_DELAY,
		MIN_SPEED, MAX_SPEED, 2, SCROLL_SIZE,
		(XtCallbackProc) speedChangeCallback);
#endif
	labelRowCol = XtVaCreateManagedWidget("labelRowCol",
		xmRowColumnWidgetClass, controlPanel, NULL);
	createRadio(labelRowCol, (Widget **) &modeRadio[0],
		modeStrings, modeLabel, XtNumber(modeStrings),
		(XtCallbackProc) modeCallback);
	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);
	puzzle2d = XtVaCreateManagedWidget("pyraminx",
		pyraminx2dWidgetClass, mainPanel,
		XtNheight, WINDOW_HEIGHT, NULL);
#ifdef HAVE_OPENGL
	puzzle3d = XtVaCreateManagedWidget("pyraminx",
		pyraminxGLWidgetClass, mainPanel,
		XtNheight, WINDOW_HEIGHT, NULL);
	XtAddCallback(puzzle3d, XtNselectCallback,
		(XtCallbackProc) puzzleCallback, (XtPointer) NULL);
#endif
#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, NULL,
		fileTypes, "File", numFileTypes,
		0, False, fileMenuCallback);
	playLabel = XtVaCreateManagedWidget("Play",
		menuButtonWidgetClass, menuBar,
		XtNborderWidth, 0,
		XtNfromHoriz, fileLabel, NULL);
	playMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, playLabel, NULL);
	for (i = 0; i < numPlayTypes; i++) {
		if (i == numPlayTypes - 1) {
			w = XtVaCreateManagedWidget(playTypes[i],
				smeBSBObjectClass, playMenu,
				XtNleftMargin, 20, NULL); /* for check */
			soundMenuItem = w;
		} else
			w = XtVaCreateManagedWidget(playTypes[i],
				smeBSBObjectClass, playMenu, NULL);
		XtAddCallback(w,
			XtNcallback, (XtCallbackProc) playMenuCallback,
			(XtPointer) (size_t) i);
	}
	createMenu(menuBar, &helpLabel, 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);
#ifdef USE_SPIN
	speedBox = XtVaCreateManagedWidget("speedBox",
		boxWidgetClass, changerRowCol,
		XtNorientation, XtorientHorizontal,
		XtNborderWidth, 0,
		XtNfromHoriz, facetBox, NULL);
	modeRowCol = XtVaCreateManagedWidget("modeRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, changerRowCol, NULL);
#else
	speedChangerRowCol = XtVaCreateManagedWidget("speedChangerRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, changerRowCol, NULL);
	speedBox = XtVaCreateManagedWidget("speedBox",
		boxWidgetClass, speedChangerRowCol,
		XtNorientation, XtorientHorizontal,
		XtNborderWidth, 0, NULL);
	modeRowCol = XtVaCreateManagedWidget("modeRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, speedChangerRowCol, NULL);
#endif
	modeBox = XtVaCreateManagedWidget("modeBox",
		boxWidgetClass, modeRowCol,
		XtNorientation, XtorientHorizontal,
		XtNborderWidth, 0, NULL);
	toggleRowCol = XtVaCreateManagedWidget("toggleRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, modeRowCol, NULL);
	orientBox = XtVaCreateManagedWidget("Oriented",
		boxWidgetClass, toggleRowCol,
		XtNorientation, XtorientHorizontal,
		XtNstate, DEFAULT_ORIENT,
		XtNborderWidth, 0, NULL);
	stickyBox = XtVaCreateManagedWidget("Sticky",
		boxWidgetClass, toggleRowCol,
		XtNorientation, XtorientHorizontal,
		XtNstate, DEFAULT_STICKY,
		XtNborderWidth, 0,
		XtNfromHoriz, orientBox, NULL);
	practiceBox = XtVaCreateManagedWidget("Practice",
		boxWidgetClass, toggleRowCol,
		XtNorientation, XtorientHorizontal,
		XtNstate, DEFAULT_PRACTICE,
		XtNborderWidth, 0,
		XtNfromHoriz, stickyBox, NULL);
	message = XtVaCreateManagedWidget("Welcome",
		labelWidgetClass, controlPanel,
		XtNjustify, XtJustifyLeft,
		XtNborderWidth, 0,
		XtNwidth, 180,
		XtNfromVert, toggleRowCol, NULL);
	puzzle2d = XtVaCreateManagedWidget("pyraminx",
		pyraminx2dWidgetClass, mainPanel,
		XtNfromVert, controlPanel,
		XtNheight, WINDOW_HEIGHT, NULL);
	XtVaGetValues(puzzle2d,
		XtNsize, &size,
		XtNmode, &mode,
		XtNorient, &orient,
		XtNsticky, &sticky,
		XtNpractice, &practice,
		XtNdelay, &delay, NULL);
#ifdef HAVE_OPENGL
	puzzle3d = XtVaCreateManagedWidget("pyraminx",
		pyraminxGLWidgetClass, mainPanel,
		XtNfromVert, puzzle2d,
		XtNheight, WINDOW_HEIGHT, NULL);
	XtAddCallback(puzzle3d, XtNselectCallback,
		(XtCallbackProc) puzzleCallback, (XtPointer) NULL);
#endif
#ifdef USE_POPUP
	createPopupMenu(modeBox, &modeNameLabel, modeStrings,
		modeLabel, 0, mode - PERIOD2,
		sizeof(modeStrings) / sizeof(*modeStrings),
		(XtCallbackProc) modeCallback);
#else
	createRadio(modeBox, (Widget **) &modeRadio[0], modeStrings,
		modeLabel, 0, mode - PERIOD2,
		sizeof(modeStrings) / sizeof(*modeStrings),
		(XtCallbackProc) modeCallback);
#endif
#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
	puzzle2d = XtVaCreateManagedWidget("pyraminx",
		pyraminx2dWidgetClass, topLevel, NULL);
#ifdef HAVE_OPENGL
	shell = XtAppCreateShell(progDsp, titleDsp,
		topLevelShellWidgetClass, XtDisplay(topLevel), NULL, 0);
	puzzle3d = XtVaCreateManagedWidget("pyraminx",
		pyraminxGLWidgetClass, shell, NULL);
	XtAddCallback(puzzle3d, XtNselectCallback,
		(XtCallbackProc) puzzleCallback, (XtPointer) NULL);
#endif
#endif
	XtVaGetValues(puzzle2d,
		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(puzzle2d, 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(puzzle2d);
	XtRealizeWidget(topLevel);
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA) && defined(HAVE_OPENGL)
	XtRealizeWidget(shell);
#endif
	XGrabButton(XtDisplay(puzzle2d), (unsigned int) AnyButton, AnyModifier,
		XtWindow(puzzle2d), TRUE, (unsigned int) (ButtonPressMask |
		ButtonMotionMask | ButtonReleaseMask),
		GrabModeAsync, GrabModeAsync, XtWindow(puzzle2d),
		XCreateFontCursor(XtDisplay(puzzle2d), XC_hand2));
#ifdef HAVE_EDITRES
	XtAddEventHandler(topLevel, (EventMask) 0, TRUE,
		(XtEventHandler) _XEditResCheckMessages, NULL);
#endif
	XtAppMainLoop(appCon);

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