/*
 * @(#)xpanex.c
 *
 * Copyright 1996 - 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 Panex */

#ifndef WINVER
#include "version.h"
static const char aboutHelp[] = {
"Panex 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\n"
"I got some help from Rene Jansen <rene.j.jansen AT bigfoot.com> "
"and Nick Baxter <nickb AT baxterweb.com>."
};

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"
"[-tile {color}] [-pyramid{0|1} {color}] [-delay msecs]\n"
"[-[no]sound] [-moveSound {filename}]\n"
"[-{font|fn} {fontname}]\n"
"[-tiles {int}] [-{mode {int}|hanoi|algorithme|panex}]\n"
"[-userName {string}] [-scoreFile {filename}] [-scores]\n"
"[-version]"
};
#endif

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA) || defined(WINVER)
static const char descriptionHelp[] = {
"Panex - A grooved sliding tile puzzle created by Toshio "
"Akanuma and manufactured by the Tricks Co., Ltd of Tokyo,\n"
"Japan (a Magic Company) in the 1980's.  Mathematicians at "
"Bell Laboratories calculated the number of moves to be 27,564\n"
"to 31,537.  It came in two varieties: one with a magenta and "
"a orange pyramid of order 10 on silver tiles; in the gold\n"
"version pieces of each color look alike (i.e. no pyramid is "
"drawn on them), this is a little harder.  The goal in this\n"
"puzzle is to simply exchange the 2 piles.  Pieces with "
"smaller trapazoids cannot go down as far as pieces with\n"
"bigger trapazoids.\n"
"\n"
"The original Tower of Hanoi puzzle is the invention of "
"Edouard Lucas and was sold as a toy in France in 1883.  The \n"
"legend of 64 disks in the great temple of Benares of the god "
"Brahma is also his invention.  The goal in this puzzle is to\n"
"move the pile from the left side to the right most column.  "
"Unlike panex, a large trapazoid cannot go on top of a smaller\n"
"one, but pieces always fall to the bottom.\n"
"\n"
"The original Algorithme 6 is 2 stacks of 3 wooden spheres on "
"2 of 3 posts.  The spheres come in 3 different sizes.  The goal\n"
"goal is to swap the spheres using the posts without putting a "
"bigger sphere on a smaller one and without exceeding the\n"
"size of the post.  It was created and produced by Patrick "
"Farvacque around 1997.  The puzzle presented here has a\n"
"simpler solution because the tiles are all the same height "
"(i.e. a 39 move solution as opposed to 66).\n"
};

static const char featuresHelp[] = {
"Press \"mouse-left\" button to move a tile in the top tile "
"of a column.  Release \"mouse-left\" button on\n"
"another column to move the tile to that column.  It will "
"not move if blocked.\n"
"\n"
"Click \"mouse-right\" button, or press \"C\" or \"c\" keys, "
"to clear the puzzle.\n"
"\n"
"Press \"G\" or \"g\" keys to get (read) 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 \"S\" or \"s\" keys to auto-solve.  Unfortunately, its "
"only implemented from the starting position.\n"
"\n"
"Press \"M\" or \"m\" keys to switch between Hanoi "
"(one pyramid column), Algorithme and Panex (each has\n"
"two pyramid columns) modes (they each have different rules).  "
"In Hanoi, one cannot place larger trapezoid on a smaller\n"
"trapezoid.  Here the goal is to move the pile from the left "
"peg to the rightmost peg.  Algorithme is similar, here we\n"
"must exchange tiles and we are limited by the size of the "
"stack.  A move from stack 1 to stack 3 and vice-versa when\n"
"stack 2 is full.  In Panex, a tile cannot go lower that "
"its initial starting point.  Here again, the goal is to\n"
"exchange the 2 piles.\n"
"\n"
"Press \"I\" or \"i\" keys to increase the number of tiles.\n"
"\n"
"Press \"D\" or \"d\" keys to decrease the number of tiles.\n"
"\n"
"Press \">\" or \".\" keys to speed up the movement of tiles.\n"
"\n"
"Press \"<\" or \",\" keys to slow down the movement of tiles.\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"
"Unlike other puzzles in the collection there is no way to "
"move pieces without drag and drop."
};

static const char referencesHelp[] = {
"Mark Manasse & Danny Sleator of AT&T Bell Laboratories "
"and Victor K. Wei of Bell Communications Research,\n"
"Some Results on the Panex Puzzle, Murray Hill, NJ, 1985 "
"20 pp. (unpublished).\n"
"Vladimir Dubrovsky, Nesting Puzzles Part 1: Moving oriental "
"towers, Quantum/Toy Store, January/February 1996 pp 55-57,\n"
"50-51.\n"
"L. E. Horden, Sliding Piece Puzzles (Recreations in "
"Mathematics Series), Oxford University Press 1986,\n"
"pp 144, 145.\n"
"Jerry Slocum & Jack Botermans, Puzzles Old & New (How to Make "
"and Solve Them), University of Washington\n"
"Press, Seattle 1987, p 135.\n"
"Dick Hess, Analysis of the Algorithme 6 Puzzle and its "
"Generalisations, Cubism For Fun, July 2008 76 pp 8-13."
};
#endif

#include "file.h"
#ifdef WINVER
#include "PanexP.h"
#define TITLE "wpanex"

static PanexRec 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->panex.started = b
#else
#include "xwin.h"
#include <X11/Shell.h>
#include <X11/cursorfont.h>
#ifdef HAVE_MOTIF
#include <Xm/PanedW.h>
#include <Xm/RowColumn.h>
#include <Xm/Label.h>
#include <Xm/LabelG.h>
#include <Xm/MessageB.h>
#include <Xm/PushBG.h>
#include <Xm/CascadeB.h>
#ifdef USE_SPIN
#include <Xm/SSpinB.h>
#else
#include <Xm/Scale.h>
#endif
#include <Xm/ToggleB.h>
#include <Xm/ToggleBG.h>
#include <Xm/Text.h>
#include <Xm/Form.h>
#ifdef MOUSEBITMAPS
#include "pixmaps/mouse-l.xbm"
#include "pixmaps/mouse-r.xbm"
#endif
#elif defined(HAVE_XAW3DXFT)
#include <X11/Xaw3dxft/Form.h>
#include <X11/Xaw3dxft/Paned.h>
#include <X11/Xaw3dxft/Label.h>
#include <X11/Xaw3dxft/Box.h>
#include <X11/Xaw3dxft/Scrollbar.h>
#include <X11/Xaw3dxft/SimpleMenu.h>
#include <X11/Xaw3dxft/MenuButton.h>
#include <X11/Xaw3dxft/SmeBSB.h>
#include <X11/Xaw3dxft/SmeLine.h>
#include <X11/Xaw3dxft/Command.h>
#include <X11/Xaw3dxft/Dialog.h>
#include <X11/Xaw3dxft/AsciiText.h>
#elif defined(HAVE_XAW3D)
#include <X11/Xaw3d/Form.h>
#include <X11/Xaw3d/Paned.h>
#include <X11/Xaw3d/Label.h>
#include <X11/Xaw3d/Box.h>
#include <X11/Xaw3d/Scrollbar.h>
#include <X11/Xaw3d/SimpleMenu.h>
#include <X11/Xaw3d/MenuButton.h>
#include <X11/Xaw3d/SmeBSB.h>
#include <X11/Xaw3d/SmeLine.h>
#include <X11/Xaw3d/Command.h>
#include <X11/Xaw3d/Dialog.h>
#include <X11/Xaw3d/AsciiText.h>
#elif defined(HAVE_NEXTAW)
#include <X11/neXtaw/Form.h>
#include <X11/neXtaw/Paned.h>
#include <X11/neXtaw/Label.h>
#include <X11/neXtaw/Toggle.h>
#include <X11/neXtaw/Box.h>
#include <X11/neXtaw/Scrollbar.h>
#undef XtCDelay
#include <X11/neXtaw/SimpleMenu.h>
#include <X11/neXtaw/MenuButton.h>
#include <X11/neXtaw/SmeBSB.h>
#include <X11/neXtaw/SmeLine.h>
#include <X11/neXtaw/Command.h>
#include <X11/neXtaw/Dialog.h>
#include <X11/neXtaw/AsciiText.h>
#elif defined(HAVE_ATHENA)
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/MenuButton.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/SmeLine.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Dialog.h>
#include <X11/Xaw/AsciiText.h>
#endif
#define SET_STARTED(w,b) XtVaSetValues(w, XtNstart, b, NULL)
#include "Panex.h"
#ifdef HAVE_XPM
#include <X11/xpm.h>
#ifdef CONSTPIXMAPS
#include "16x16/panex.xpm"
#include "22x22/panex.xpm"
#include "24x24/panex.xpm"
#include "32x32/panex.xpm"
#include "48x48/panex.xpm"
#include "64x64/panex.xpm"
#else
#include "pixmaps/16x16/panex.xpm"
#include "pixmaps/22x22/panex.xpm"
#include "pixmaps/24x24/panex.xpm"
#include "pixmaps/32x32/panex.xpm"
#include "pixmaps/48x48/panex.xpm"
#include "pixmaps/64x64/panex.xpm"
#endif
#define RESIZE_XPM(s) ((char **) (((s)<=32)?\
(((s)<=22)?(((s)<=16)?panex_16x16:panex_22x22):\
(((s)<=24)?panex_24x24:panex_32x32)):\
(((s)<=48)?panex_48x48:panex_64x64)))
#endif
#include "pixmaps/64x64/panex.xbm"
#define DEFINE_XBM (char *) panex_64x64_bits, panex_64x64_width, panex_64x64_height
#ifndef SCOREPATH
#ifdef VMS
#define SCOREPATH "SYS$LOGIN:"
#else
#define SCOREPATH "/var/games/xpuzzles"
#endif
#endif
#endif

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

#define MAX_TILES 10
#define NEVER (-1)
#define FILE_NAME_LENGTH 1024
#define TITLE_FILE_LENGTH (FILE_NAME_LENGTH+8)
#define USER_NAME_LENGTH 120
#define MESSAGE_LENGTH (USER_NAME_LENGTH+64)
#define TITLE_LENGTH 2048
#define NOACCESS "noaccess"
#define NOBODY "nobody"

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

static PuzzleRecord puzzleRecord[MAX_MODES][MAX_TILES - MIN_TILES + 1];
static int movesDsp = 0;
static char messageDsp[MESSAGE_LENGTH] =
	"Welcome                        ";
static char recordDsp[MESSAGE_LENGTH] = "NOT RECORDED";
static const char *modeString[] =
{
	"Hanoi", "Algorithme", "Panex"
};

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
static Widget movesText, recordText, message;
static Widget tileChanger, speedChanger;
static Widget soundMenuItem;
static Widget clearDialog;
static Widget descriptionDialog, featuresDialog;
static Widget synopsisDialog, referencesDialog, aboutDialog;
static char buff[MESSAGE_LENGTH];
static char *tilesLabel = (char *) "Tiles:";
static char *speedLabel = (char *) "Animation Speed:";
#define PRINT_MESSAGE(b) printState(message, b)
#define MIN_SPEED 1
#define MAX_SPEED 50
#elif !defined(WINVER)
#define PRINT_MESSAGE(b) XtWarning(b)
#endif
#ifdef HAVE_MOTIF
#define SCROLL_SIZE 30 /* A page */
static Widget modes[MAX_MODES];
static Arg args[10];
#elif defined(HAVE_ATHENA)
#define SCROLL_SIZE 100
static Atom wmDeleteWindow = None;
static Widget modeLabel, modeMenu;
static Widget tileSlider, speedSlider;
static Widget tileSliderLabel, speedSliderLabel;
static char *fileTypes[] =
{
	(char *) "Get",
	(char *) "Write",
	(char *) "Exit"
};
#define numFileTypes (sizeof(fileTypes)/sizeof(fileTypes[0]))
static char *playTypes[] =
{
	(char *) "Undo",
	(char *) "Redo",
	(char *) "Clear",
	(char *) "Auto-solve",
	(char *) "Mode (Hanoi/Algorithme/Panex)",
#ifdef EXTRA
	(char *) "Increment",
	(char *) "Decrement",
	(char *) "Speed >",
	(char *) "Slow <",
#endif
	(char *) "Sound @"
};
#define numPlayTypes (sizeof(playTypes)/sizeof(playTypes[0]))
static char *helpTypes[] =
{
	(char *) "Description...",
	(char *) "Features...",
	(char *) "Synopsis...",
	(char *) "References...",
	(char *) "About..."
};
#define numHelpTypes (sizeof(helpTypes)/sizeof(helpTypes[0]))
#include "pixmaps/check.xbm"
#else
static char titleDsp[TITLE_LENGTH] = "";
#endif
static char scoreFileName[FILE_NAME_LENGTH] = SCOREFILE;
static char fileName[FILE_NAME_LENGTH];
#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 *progDsp;
static char userNameDsp[USER_NAME_LENGTH] = "";

#ifdef HAVE_MOTIF
static void
updateToggle(Widget menu, Widget *menuItem, Boolean toggle, int number,
		XtCallbackProc callback)
{
	char button[10];

	(void) sprintf(button, "button_%d", number);
	if ((*menuItem = XtNameToWidget(menu, button)) != (Widget) 0) {
		if (toggle)
			XtVaSetValues(*menuItem,
				XmNset, toggle, NULL);
		XtAddCallback(*menuItem, XmNvalueChangedCallback, callback,
			(XtPointer) NULL);
	}
}

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);
}

static void
setLabel(Widget label, char *prefix, int value)
{
	char buff[BUFSIZ];

	(void) sprintf(buff, "%s %2d", prefix, value);
	XtVaSetValues(label,
		XtNlabel, buff, NULL);
}

static void
setScale(Widget slider, Widget label, char *prefix,
		int value, int min, int max)
{
	XawScrollbarSetThumb(slider, ((float) (value - min)) /
		(max - min), 0.0);
	setLabel(label, prefix, value);
}

static void
createBlank(char **string, int size, char *defaultString,
		int side) {
	int i, defSize = strlen(defaultString) + 1, midSize;
	char * val;

	*string = (char *) malloc(size);
	val = *string;
	if (side == 0) /* middle */
		midSize = (size - defSize) >> 1;
	else /* right justified */
		midSize = size - defSize;
	for (i = 0; i < size - 1; i++) {
		if (i - midSize >= 0 &&
				(i - midSize < defSize - 1))
			val[i] = defaultString[i - midSize];
		else
			val[i] = ' ';
	}
	val[size - 1] = '\0';
}

static int
findMaxLength(char **list, int num) {
	int i, temp, max;

	max = 0;
	for (i = 0; i < num; i++) {
		temp = strlen(list[i]);
		if (temp > max)
			max = temp;
	}
	return ++max;
}
#endif

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

	(void) printf("HANOI/ALGORITHME/PANEX  HALL OF FAME\n\n");
	(void) printf("PANEX TILES USER     MOVES\n");
	for (mode = 0; mode < MAX_MODES; mode++)
		for (i = 0; i < MAX_TILES - MIN_TILES + 1; i++) {
			if (puzzleRecord[mode][i].score > 0)
				(void) printf("%5d%6d %-12s%9d\n",
					mode, i + 1,
					puzzleRecord[mode][i].name,
					puzzleRecord[mode][i].score);
		}
}
#endif

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

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

static void
readRecords(void)
{
	FILE *fp;
	int i, mode, n;
	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 (i = 0; i < MAX_TILES - MIN_TILES + 1; i++) {
			if (fscanf(fp, "%d %s\n", &n, userName) != 2) {
				(void) fprintf(stderr,
					"corrupt record, expecting integer and name for %d %d\n",
					mode, i);
				(void) fclose(fp);
				return;
			}
			if (n <= puzzleRecord[mode][i].score ||
					puzzleRecord[mode][i].score <= NEVER) {
				puzzleRecord[mode][i].score = n;
				(void) strncpy(puzzleRecord[mode][i].name,
					userName, USER_NAME_LENGTH);
			}
		}
	(void) fclose(fp);
}

static void
writeRecords(void)
{
	FILE *fp;
	int i, mode;
	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 (i = 0; i < MAX_TILES - MIN_TILES + 1; i++)
				(void) fprintf(fp, "%d %s\n",
					puzzleRecord[mode][i].score,
					puzzleRecord[mode][i].name);
			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 tiles, int mode)
{
	int i = tiles - MIN_TILES, j = mode - HANOI;

	if (tiles > MAX_TILES)
		(void) strncpy(recordDsp, "NOT RECORDED", 13);
	else if (puzzleRecord[j][i].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[j][i].score,
			puzzleRecord[j][i].name);
#else
		(void) sprintf(recordDsp, "%d %s",
			puzzleRecord[j][i].score,
			puzzleRecord[j][i].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 tiles, int mode
#endif
		)
{
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	printState(message, msg);
	intCpy(buff, nMoves);
	printState(movesText, buff);
#else
#ifdef HAVE_SNPRINTF
	(void) snprintf(titleDsp, TITLE_LENGTH,
		"%s [%s]: %d@ (%d/%s) - %s",
		progDsp, modeString[mode - HANOI], tiles,
		nMoves, recordDsp, msg);
#else
	(void) sprintf(titleDsp,
		"%s [%s]: %d@ (%d/%s) - %s",
		progDsp, modeString[mode], tiles,
		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 tiles, int mode)
{
	int i = tiles - MIN_TILES, j = mode - HANOI;

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

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
static void
concatTitle(char *newTitle, char *title,
		const char *joinString, const char *string, int n)
{
	int stringLen = strlen(string);
	int joinStringLen = strlen(joinString);
	int len = n - stringLen - joinStringLen;

	if (len > 0) {
		(void) strncpy(newTitle, title, len);
		newTitle[len - 1] = '\0';
		(void) strcat(newTitle, joinString);
		(void) strcat(newTitle, string);
	} else {
		(void) strncpy(newTitle, title, n - 1);
		newTitle[n - 1] = '\0';
	}
}

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

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	int limit;
	tiles = cbs->position;
#else
	tiles = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	long position = (size_t) callData; /* left or right button */
	tiles = ABS(position) *
		(MAX_TILES - MIN_TILES) / SCROLL_SIZE + MIN_TILES;
	if (tiles < MIN_TILES || tiles > MAX_TILES) {
		/* middle button */
		tiles = (int) (*((float *) callData) *
			(MAX_TILES - MIN_TILES)) + MIN_TILES;
		if (tiles < MIN_TILES || tiles > MAX_TILES)
			return;
	}
#endif
	XtVaGetValues(puzzle,
		XtNtiles, &old,
		XtNmode, &mode, NULL);
	if (old == tiles)
		return;
#if defined(HAVE_MOTIF) && defined(USE_SPIN)
	XtVaGetValues(tileChanger,
		XmNmaximumValue, &limit, NULL);
	if (tiles >= limit)
		XtVaSetValues(tileChanger,
			XmNmaximumValue, tiles + 1,
			XmNposition, tiles, NULL);
#elif defined(HAVE_ATHENA)
	setScale(w, label, tilesLabel, tiles, MIN_TILES, MAX_TILES);
#endif
	XtVaSetValues(puzzle,
		XtNtiles, tiles, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(tiles, mode);
	messageDsp[0] = '\0';
	printState(message, messageDsp);
}

static void
speedChangeListener(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;
	long position = (size_t) callData; /* left or right button */
	speed = ABS(position) *
		(MAX_SPEED - MIN_SPEED) / SCROLL_SIZE + MIN_SPEED;
	if (speed < MIN_SPEED || speed > MAX_SPEED) {
		/* middle button */
		speed = (int) (*((float *) callData) *
			(MAX_SPEED - MIN_SPEED)) + MIN_SPEED;
		if (speed < MIN_SPEED || speed > MAX_SPEED)
			return;
	}
#endif
	delay = MAX_SPEED + MIN_SPEED - speed - 1;
	XtVaGetValues(puzzle,
		XtNdelay, &old, NULL);
	if (old == delay)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, speedLabel, speed, MIN_SPEED, MAX_SPEED);
#endif
	XtVaSetValues(puzzle,
		XtNdelay, delay, NULL);
}

static void
modeListener(Widget w,
#ifdef HAVE_MOTIF
		int modeIndex, XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		XtPointer clientData, XtPointer callData
#endif
		)
{
	int tiles, mode, old;
#ifdef HAVE_MOTIF
	if (!cbs->set)
		return;
#elif defined(HAVE_ATHENA)
	int modeIndex = ((size_t) clientData);
#endif

	XtVaGetValues(puzzle,
		XtNtiles, &tiles,
		XtNmode, &old, NULL);
	mode = modeIndex + HANOI;
	if (old == mode)
		return;
#ifdef HAVE_ATHENA
	XtVaSetValues(modeLabel,
		XtNlabel, modeString[modeIndex], NULL);
#endif
	XtVaSetValues(puzzle,
		XtNmode, mode, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(tiles, mode);
	messageDsp[0] = '\0';
	printState(message, messageDsp);
}

static void
puzzleClearListener(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(clearDialog);
#endif
		XtVaSetValues(puzzle,
			XtNmenu, ACTION_CLEAR, NULL);
}

static void
fileMenuListener(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
syncSoundCheck(Widget w)
{
#if HAVE_ATHENA
	static Pixmap checkPixmap = None;
#endif
	Boolean sound;
	XtVaGetValues(puzzle,
		XtNsound, &sound, NULL);
#ifdef HAVE_MOTIF
	XmToggleButtonSetState(w, sound, False);
#else
	if (checkPixmap == None)
		checkPixmap = XCreateBitmapFromData(XtDisplay(topLevel),
			RootWindow(XtDisplay(topLevel),
			DefaultScreen(XtDisplay(topLevel))),
			(char *)check_bits, check_width, check_height);
	XtVaSetValues(w,
		XtNrightBitmap, ((sound) ? checkPixmap : None), NULL);
#endif
}

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

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

static void
helpMenuListener(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, "helpMenuListener: %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
PanexWidget w, HBRUSH brush
#else
Widget w
#endif
)
{
	int tiles, mode;
	char *userName, *scoreFile;

#ifdef WINVER
	initializePuzzle(w, brush);

	tiles = w->panex.tiles;
	mode = w->panex.mode;
	userName = w->panex.userName;
	scoreFile = w->panex.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,
		XtNtiles, &tiles,
		XtNmode, &mode,
		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(tileChanger,
			XmNmaximumValue, &limit, NULL);
		if (tiles >= limit)
			XtVaSetValues(tileChanger,
				XmNposition, tiles,
				XmNmaximumValue, tiles + 1, NULL);
		else
			XtVaSetValues(tileChanger,
				XmNposition, tiles, NULL);
	}
#else
	if (tiles > MAX_TILES)
		XtVaSetValues(tileChanger,
			XmNmaximum, tiles, NULL);
	XmScaleSetValue(tileChanger, tiles);
#endif
	XmToggleButtonSetState(modes[mode - HANOI], True, False);
	{
		int delay, speed;
		XtVaGetValues(puzzle,
			XtNdelay, &delay, NULL);
		speed = MAX_SPEED + MIN_SPEED - delay - 1;
#ifdef USE_SPIN
		XtVaSetValues(speedChanger,
			XmNposition, speed, NULL);
#else
		XmScaleSetValue(speedChanger, speed);
#endif
	}
	syncSoundCheck(soundMenuItem);
#elif defined(HAVE_ATHENA)
        {
                int delay, speed;
                XtVaGetValues(w,
                        XtNdelay, &delay, NULL);
                speed = MAX_SPEED + MIN_SPEED - delay - 1;
                setScale(speedSlider, speedSliderLabel, speedLabel,
                        speed, MIN_SPEED, MAX_SPEED);
        }
	syncSoundCheck(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(tiles, mode);
	printStatus(messageDsp, movesDsp
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		, tiles, mode
#endif
		);
}

#ifdef WINVER
void
setPuzzle(PanexWidget w, int reason)
#else
static void
puzzleListener(Widget w, caddr_t clientData,
		panexCallbackStruct *callData)
#endif
{
#ifndef WINVER
	int reason = callData->reason;
#endif
#if defined(HAVE_MOTIF) && defined(USE_SPIN)
	int limit;
#elif defined(HAVE_ATHENA)
	int max;
	char *defaultString;
#endif
	int tiles, mode;
	Boolean cheat;

	messageDsp[0] = '\0';
#ifdef WINVER
	tiles = w->panex.tiles;
	mode = w->panex.mode;
	cheat = w->panex.cheat;
#else
	XtVaGetValues(w,
		XtNtiles, &tiles,
		XtNmode, &mode,
		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;
#if !defined(WINVER) && defined(CLEAR_QUERY)
	case ACTION_CLEAR_QUERY:
#ifdef HAVE_MOTIF
		XtManageChild(clearDialog);
#elif defined(HAVE_ATHENA)
		XtPopup(clearDialog);
		XSetWMProtocols(XtDisplay(topLevel),
			XtWindow(clearDialog), &wmDeleteWindow, 1);
#else
		XtVaSetValues(puzzle, XtNmenu, ACTION_CLEAR, NULL);
#endif
		break;
#endif
	case ACTION_RESTORE:
	case ACTION_RESET:
		movesDsp = 0;
		break;
	case ACTION_ILLEGAL:
		(void) strncpy(messageDsp, "Illegal move", MESSAGE_LENGTH);
		break;
	case ACTION_BLOCKED:
		(void) strncpy(messageDsp, "Blocked", MESSAGE_LENGTH);
		break;
	case ACTION_SPACE:
#if 0
		/* Too annoying */
		(void) strncpy(messageDsp, "Spaces cannot slide",
			MESSAGE_LENGTH);
#endif
		break;
	case ACTION_IGNORE:
		(void) strncpy(messageDsp, "Clear to start",
			MESSAGE_LENGTH);
		break;
	case ACTION_MOVED:
		movesDsp++;
		SET_STARTED(w, True);
		break;
	case ACTION_SOLVED:
		if (cheat) {
			(void) strncpy(messageDsp, "No cheating ", 13);
			(void) strncat(messageDsp, userNameDsp, USER_NAME_LENGTH);
			(void) strncat(messageDsp, "!!", 3);
		} else if (handleSolved(movesDsp, tiles, mode)) {
			(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);
		break;
	case ACTION_COMPUTED:
		SET_STARTED(w, False);
		break;
	case ACTION_INCREMENT:
		movesDsp = 0;
		tiles++;
		printRecord(tiles, mode);
#ifdef WINVER
		w->panex.tiles = tiles;
#else
		XtVaSetValues(w, XtNtiles, tiles, NULL);
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		XtVaGetValues(tileChanger,
			XmNmaximumValue, &limit, NULL);
		if (tiles >= limit)
			XtVaSetValues(tileChanger,
				XmNmaximumValue, tiles + 1,
				XmNposition, tiles, NULL);
		else
			XtVaSetValues(tileChanger,
				XmNposition, tiles, NULL);
#else
		if (tiles > MAX_TILES)
			XtVaSetValues(tileChanger,
				XmNmaximum, tiles, NULL);
		XmScaleSetValue(tileChanger, tiles);
#endif
#elif defined(HAVE_ATHENA)
		setScale(tileSlider, tileSliderLabel, tilesLabel, tiles,
			MIN_TILES, MAX_TILES);
#endif
#endif
		break;
	case ACTION_DECREMENT:
		movesDsp = 0;
		tiles--;
		printRecord(tiles, mode);
#ifdef WINVER
		w->panex.tiles = tiles;
#else
		XtVaSetValues(w, XtNtiles, tiles, NULL);
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		if (tiles > MAX_TILES)
			XtVaSetValues(tileChanger,
				XmNmaximumValue, tiles,
				XmNposition, tiles, NULL);
		else
			XtVaSetValues(tileChanger,
				XmNposition, tiles, NULL);
#else
		if (tiles > MAX_TILES)
			XtVaSetValues(tileChanger,
				XmNmaximum, tiles, NULL);
		XmScaleSetValue(tileChanger, tiles);
#endif
#elif defined(HAVE_ATHENA)
		setScale(tileSlider, tileSliderLabel, tilesLabel, tiles,
			MIN_TILES, MAX_TILES);
#endif
#endif
		break;
	case ACTION_MODE:
		movesDsp = 0;
		mode++;
		if (mode > PANEX)
			mode = HANOI;
		printRecord(tiles, mode);
#ifdef WINVER
		w->panex.mode = mode;
#else
		XtVaSetValues(w, XtNmode, mode, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(modes[mode - HANOI], True,
			True);
#elif defined(HAVE_ATHENA)
		max = findMaxLength((char **) modeString,
			sizeof(modeString) / sizeof(*modeString));
		createBlank(&defaultString, max, (char *) modeString[mode], 0);
		XtVaSetValues(modeLabel,
			XtNlabel, defaultString, NULL);
		free(defaultString);
#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 = MAX_SPEED + MIN_SPEED - delay - 1;
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
			XtVaSetValues(speedChanger,
				XmNposition, speed, NULL);
#else
			XmScaleSetValue(speedChanger, speed);
#endif
#elif defined(HAVE_ATHENA)
			setScale(speedSlider, speedSliderLabel, speedLabel,
				speed, MIN_SPEED, MAX_SPEED);
#endif
		}
		return;
	case ACTION_SOUND:
		syncSoundCheck(soundMenuItem);
		break;
#endif
	}
	printStatus(messageDsp, movesDsp
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		, tiles, mode
#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;
	static Boolean mousePressed = False;

	widget.core.hWnd = hWnd;
	if (GetFocus()) {
		if (!widget.panex.focus) {
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_BRUSH));
			enterPuzzle(&widget);
			(void) EndPaint(hWnd, &paint);
		}
	} else {
		if (widget.panex.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));
		clearPuzzle(&widget);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_LBUTTONDOWN:
		mousePressed = True;
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		selectPuzzle(&widget, LOWORD(lParam));
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_MOUSEMOVE:
		if (!mousePressed)
			break;
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		motionPuzzle(&widget, LOWORD(lParam));
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_LBUTTONUP:
		mousePressed = False;
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		releasePuzzle(&widget, LOWORD(lParam));
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	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_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_MODE:
			(void) modePuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_INCREMENT:
		case ACTION_DECREMENT:
			((LOWORD(wParam) == ACTION_DECREMENT) ?
				decrementPuzzle(&widget) :
				incrementPuzzle(&widget));
			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 void
usage(char *programName)
{
	unsigned int i;

	(void) fprintf(stderr, "usage: %s\n", programName);
	for (i = 0; i < strlen(synopsisHelp); i++) {
		if (i == 0 || synopsisHelp[i - 1] == '\n')
			(void) fprintf(stderr, "\t");
		(void) fprintf(stderr, "%c", (synopsisHelp[i]));
	}
	(void) fprintf(stderr, "\n");
	exit(1);
}

static XrmOptionDescRec options[] =
{
	{(char *) "-mono", (char *) "*panex.mono", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nomono", (char *) "*panex.mono", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-rv", (char *) "*panex.reverseVideo", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-reverse", (char *) "*panex.reverseVideo", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-norv", (char *) "*panex.reverseVideo", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-noreverse", (char *) "*panex.reverseVideo", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-fg", (char *) "*panex.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-foreground", (char *) "*panex.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-bg", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-background", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-tile", (char *) "*panex.tileColor", XrmoptionSepArg, NULL},
	{(char *) "-pyramid0", (char *) "*panex.pyramidColor0", XrmoptionSepArg, NULL},
	{(char *) "-pyramid1", (char *) "*panex.pyramidColor1", XrmoptionSepArg, NULL},
	{(char *) "-delay", (char *) "*panex.delay", XrmoptionSepArg, NULL},
	{(char *) "-sound", (char *) "*panex.sound", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nosound", (char *) "*panex.sound", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-moveSound", (char *) "*panex.moveSound", XrmoptionSepArg, NULL},
	{(char *) "-fn", (char *) "*panex.font", XrmoptionSepArg, NULL},
	{(char *) "-font", (char *) "*panex.font", XrmoptionSepArg, NULL},
	{(char *) "-tiles", (char *) "*panex.tiles", XrmoptionSepArg, NULL},
	{(char *) "-mode", (char *) "*panex.mode", XrmoptionSepArg, NULL},
	{(char *) "-hanoi", (char *) "*panex.mode", XrmoptionNoArg, (char *) "0"},
	{(char *) "-algorithme", (char *) "*panex.mode", XrmoptionNoArg, (char *) "1"},
	{(char *) "-panex", (char *) "*panex.mode", XrmoptionNoArg, (char *) "2"},
	{(char *) "-userName", (char *) "*panex.userName", XrmoptionSepArg, NULL},
	{(char *) "-scoreFile", (char *) "*panex.scoreFile", XrmoptionSepArg, NULL},
	{(char *) "-scores", (char *) "*panex.scoreOnly", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-version", (char *) "*panex.versionOnly", XrmoptionNoArg, (char *) "TRUE"}
};

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

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

static Widget
createQuery(Widget w, char *title, char *text, XtCallbackProc callback)
{
	Widget button, messageBox;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL, messageString = NULL;
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	titleString = XmStringCreateSimple((char *) titleDsp);
	messageString = XmStringCreateLtoR(text, charSet);
	XtSetArg(args[0], XmNdialogTitle, titleString);
	XtSetArg(args[1], XmNmessageString, messageString);
	messageBox = XmCreateWarningDialog(w, (char *) "queryBox",
		args, 2);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	XmStringFree(titleString);
	XmStringFree(messageString);
	XtAddCallback(messageBox, XmNokCallback, callback, (XtPointer) NULL);
	XtAddCallback(messageBox, XmNcancelCallback, callback,
		(XtPointer) NULL);
	return messageBox;
}

static Widget
createHelp(Widget w, char *title, char *text)
{
	Widget button, messageBox;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL, messageString = NULL, buttonString = NULL;
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;

	messageString = XmStringCreateLtoR(text, charSet);
	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	titleString = XmStringCreateSimple((char *) titleDsp);
	buttonString = XmStringCreateSimple((char *) "OK");
	XtSetArg(args[0], XmNdialogTitle, titleString);
	XtSetArg(args[1], XmNokLabelString, buttonString);
	XtSetArg(args[2], XmNmessageString, messageString);
	messageBox = XmCreateInformationDialog(w, (char *) "helpBox",
		args, 3);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_CANCEL_BUTTON);
	XtUnmanageChild(button);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	XmStringFree(titleString);
	XmStringFree(buttonString);
	XmStringFree(messageString);
	return messageBox;
}

/*http://www.ist.co.uk/motif/books/vol6A/ch-5.fm.html*/
static Widget
createScrollHelp(Widget w, const char *title, char *text)
{
	Widget dialog, pane, scrolledText, form, label, button;
	int n = 0;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL;

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	dialog = XmCreateMessageDialog(w, titleDsp, NULL, 0);
	titleString = XmStringCreateSimple((char *) titleDsp);
	pane = XtVaCreateWidget("pane", xmPanedWindowWidgetClass, dialog,
		XmNsashWidth, 1, XmNsashHeight, 1, NULL);
	form = XtVaCreateWidget("form", xmFormWidgetClass, pane, NULL);
	label = XtVaCreateManagedWidget("label", xmLabelGadgetClass, form,
		XmNlabelType, XmPIXMAP,
		XmNlabelPixmap, pixmap,
		XmNleftAttachment, XmATTACH_FORM,
		XmNtopAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM, NULL);
	XtSetArg(args[n], XmNdialogTitle, titleString); n++;
	XtSetArg(args[n], XmNscrollVertical, True); n++;
	XtSetArg(args[n], XmNscrollHorizontal, False); n++;
	XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
	XtSetArg(args[n], XmNeditable, False); n++;
	XtSetArg(args[n], XmNcursorPositionVisible, False); n++;
	XtSetArg(args[n], XmNwordWrap, False); n++;
	XtSetArg(args[n], XmNresizeWidth, True); n++;
	XtSetArg(args[n], XmNvalue, text); n++;
	XtSetArg(args[n], XmNrows, SCROLL_SIZE); n++;
	scrolledText = XmCreateScrolledText(form, (char *) "helpText", args, n);
	XtVaSetValues(XtParent(scrolledText),
		XmNleftAttachment, XmATTACH_WIDGET,
		XmNleftWidget, label,
		XmNtopAttachment, XmATTACH_FORM,
		XmNrightAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM, NULL);
	XmStringFree(titleString);
	XtManageChild(scrolledText);
	XtManageChild(form);
	XtManageChild(pane);
	button = XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON);
	XtUnmanageChild(button);
	button = XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	return dialog;
}

#elif defined(HAVE_ATHENA)

static void deleteWindowProc(Widget w, XEvent *event,
		String *pars, Cardinal *nPars)
{
	exit(0);
}

static void closePopupPanel(Widget w)
{
	XtPopdown(w);
}

static void
createTileSlider(Widget form)
{
	int tiles;

	XtVaGetValues(puzzle,
		XtNtiles, &tiles, NULL);
	tileSlider = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	tileSliderLabel = XtVaCreateManagedWidget("label",
		labelWidgetClass, form,
		XtNfromHoriz, tileSlider, NULL);
	setScale(tileSlider, tileSliderLabel, tilesLabel, tiles,
		MIN_TILES, MAX_TILES);
	XtAddCallback(tileSlider,
		XtNscrollProc, tileChangeListener,
		(XtPointer) tileSliderLabel);
	XtAddCallback(tileSlider,
		XtNjumpProc, tileChangeListener,
		(XtPointer) tileSliderLabel);
}

static void
createSpeedSlider(Widget form)
{
	int delay;

	XtVaGetValues(puzzle,
		XtNdelay, &delay, NULL);
	speedSlider = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	speedSliderLabel = XtVaCreateManagedWidget("label",
		labelWidgetClass, form,
		XtNfromHoriz, speedSlider, NULL);
	setScale(speedSlider, speedSliderLabel, speedLabel,
		MAX_SPEED + MIN_SPEED - delay - 1,
		MIN_SPEED, MAX_SPEED);
	XtAddCallback(speedSlider,
		XtNscrollProc, speedChangeListener,
		(XtPointer) speedSliderLabel);
	XtAddCallback(speedSlider,
		XtNjumpProc, speedChangeListener,
		(XtPointer) speedSliderLabel);
}

static void
createModePopupMenu(Widget form, const char **list, int init, int num)
{
	Widget w;
	int i, max;
	char *defaultString;

	max = findMaxLength((char **) list, num);
	createBlank(&defaultString, max, (char *) list[init], 0);
	w = XtVaCreateManagedWidget("Mode:",
		labelWidgetClass, form,
		XtNborderWidth, 0,
		NULL);
	modeLabel = XtVaCreateManagedWidget(defaultString,
		menuButtonWidgetClass, form,
		XtNfromHoriz, w,
		/*XtNwidth, 150,*/
		NULL);
	free(defaultString);
	modeMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, modeLabel, NULL);
	for (i = 0; i < num; i++) {
		w = XtVaCreateManagedWidget(list[i],
			smeBSBObjectClass, modeMenu, NULL);
		XtAddCallback(w, XtNcallback,
			(XtCallbackProc) modeListener, (XtPointer) (size_t) i);
	}
}

static void
puzzleClearCancelListener(Widget w, XtPointer clientData, XtPointer callData)
{
	XtPopdown(clearDialog);
}

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

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

static void
okListener(Widget w, XtPointer clientData, XtPointer callData)
{
	XtPopdown(XtParent(XtParent(w)));
}

static void
createHelp(Widget *dialogShell, char *title, char *text)
{
	Widget dialog, button;
	char titleDsp[TITLE_FILE_LENGTH];

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	*dialogShell = XtCreatePopupShell(titleDsp,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		dialogWidgetClass, *dialogShell,
		XtNlabel, text, NULL);
	button = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(button, XtNcallback,
		okListener, dialog);
	XtRealizeWidget(*dialogShell);
	XSetWMProtocols(XtDisplay(topLevel),
		XtWindow(*dialogShell), &wmDeleteWindow, 1);
}

static void
createScrollHelp(Widget *dialogShell, char *title, char *text)
{
	Widget dialog, scroll, button;
	char titleDsp[TITLE_FILE_LENGTH];

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	*dialogShell = XtCreatePopupShell(titleDsp,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("text",
		formWidgetClass, *dialogShell, NULL);
	scroll = XtVaCreateManagedWidget("text",
		asciiTextWidgetClass, dialog,
		XtNwidth, 105 * 8,
		XtNheight, 512,
		XtNscrollVertical, XawtextScrollAlways,
		XtNstring, text, NULL);
	button = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog,
		XtNfromVert, scroll, NULL);
	XtAddCallback(button, XtNcallback,
		okListener, dialog);
	XtRealizeWidget(*dialogShell);
	XSetWMProtocols(XtDisplay(topLevel),
		XtWindow(*dialogShell), &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, radioRowCol, changerRowCol;
	Widget playMenu;
#endif
#ifdef HAVE_MOTIF
	Widget pullDownMenu, widget;
	Widget menuBarPanel;
	Widget changerTilesRowCol, changerSpeedRowCol;
	Widget messageRowCol;
	XmString fileString, playString;
	XmString getString, writeString, quitString;
	XmString undoString, redoString;
	XmString clearString, solveString;
	XmString mode;
#ifdef EXTRA
	XmString incrementString, decrementString;
	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 movesLabel, recordLabel;
	Widget w, fileMenu, helpMenu;
	Widget fileLabel, playLabel, helpLabel;
	int mode;
	unsigned int i;
#endif

	progDsp = strrchr(argv[0], '/');
	if (progDsp != NULL)
		progDsp++;
	if (progDsp == NULL)
		progDsp = argv[0];
	topLevel = XtVaAppInitialize(NULL, "Panex",
		options, XtNumber(options), &argc, argv,
#ifdef HAVE_ATHENA
		fallbackResources,
#else
		NULL,
#endif
		NULL);
	appCon = XtWidgetToApplicationContext(topLevel);
	if (argc != 1)
		usage(argv[0]);
#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, fileMenuListener,
		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");
	solveString = XmStringCreateSimple((char *) "Auto-solve");
	mode = XmStringCreateSimple((char *) "Mode (Hanoi/Algorithme/Panex)");
#ifdef EXTRA
	incrementString = XmStringCreateSimple((char *) "Increment");
	decrementString = XmStringCreateSimple((char *) "Decrement");
	speedString = XmStringCreateSimple((char *) "Speed >");
	slowString = XmStringCreateSimple((char *) "Slow <");
#endif
	soundString = XmStringCreateSimple((char *) "Sound @");
	playMenu = XmVaCreateSimplePulldownMenu(menuBar, (char *) "playMenu",
		1, playMenuListener,
		XmVaPUSHBUTTON, undoString, 'U', NULL, NULL,
		XmVaPUSHBUTTON, redoString, 'R', NULL, NULL,
		XmVaPUSHBUTTON, clearString, 'C', NULL, NULL,
		XmVaPUSHBUTTON, solveString, 's', NULL, NULL,
		XmVaPUSHBUTTON, mode, 'M', NULL, NULL,
#ifdef EXTRA
		XmVaPUSHBUTTON, incrementString, 'I', NULL, NULL,
		XmVaPUSHBUTTON, decrementString, 'D', NULL, NULL,
		XmVaPUSHBUTTON, speedString, '>', NULL, NULL,
		XmVaPUSHBUTTON, slowString, '<', NULL, NULL,
#endif
		XmVaTOGGLEBUTTON, soundString, '@', NULL, NULL, NULL);
	XmStringFree(undoString);
	XmStringFree(redoString);
	XmStringFree(clearString);
	XmStringFree(solveString);
	XmStringFree(mode);
#ifdef EXTRA
	XmStringFree(incrementString);
	XmStringFree(decrementString);
	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 error on Cygwin */
	XtVaSetValues(menuBar, XmNmenuHelpWidget, widget, NULL);
	widget = XtVaCreateManagedWidget("Description...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'D', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 0);
	widget = XtVaCreateManagedWidget("Features...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'F', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 1);
	widget = XtVaCreateManagedWidget("Synopsis...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'S', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 2);
	widget = XtVaCreateManagedWidget("References...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'R', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 3);
	widget = XtVaCreateManagedWidget("About...",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'A', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 4);
	XtManageChild(menuBar);
	clearDialog = createQuery(topLevel, (char *) "Clear Query",
		(char *) "Are you sure you want to start over?",
		(XtCallbackProc) puzzleClearListener);
	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 tile", 10, NULL);
		(void) XtVaCreateManagedWidget("mouseLeft",
			xmLabelGadgetClass, movesRowCol,
			XmNlabelType, XmPIXMAP,
			XmNlabelPixmap, mouseLeftCursor, NULL);
		(void) XtVaCreateManagedWidget("mouseRightText",
			xmLabelGadgetClass, movesRowCol,
			XtVaTypedArg, XmNlabelString,
			XmRString, "Clear", 6, 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);
	radioRowCol = XtVaCreateManagedWidget("movesRowCol",
		xmRowColumnWidgetClass, controlPanel,
		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) modeListener, (XtPointer) ((size_t) i));
	}
	changerRowCol = XtVaCreateManagedWidget("changerRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	changerTilesRowCol = XtVaCreateManagedWidget("changerTilesRowCol",
		xmRowColumnWidgetClass, changerRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(changerTilesRowCol, tilesLabel, NULL, 0));
	tileChanger = XtVaCreateManagedWidget("tileChanger",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerTilesRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_TILES,
		XmNmaximumValue, MAX_TILES,
		XmNposition, DEFAULT_TILES,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerTilesRowCol,
		XmNminimum, MIN_TILES,
		XmNmaximum, MAX_TILES,
		XmNvalue, DEFAULT_TILES,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Tiles", 6,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(tileChanger, XmNvalueChangedCallback,
		(XtCallbackProc) tileChangeListener, (XtPointer) NULL);
	changerSpeedRowCol = XtVaCreateManagedWidget("changerSpeedRowCol",
		xmRowColumnWidgetClass, changerRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(changerSpeedRowCol, speedLabel, NULL, 0));
	speedChanger = XtVaCreateManagedWidget("speedChanger",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerSpeedRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_SPEED,
		XmNmaximumValue, MAX_SPEED,
		XmNposition, MAX_SPEED - DEFAULT_DELAY,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerSpeedRowCol,
		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) speedChangeListener, (XtPointer) NULL);
	messageRowCol = XtVaCreateManagedWidget("messageRowCol",
		xmRowColumnWidgetClass, controlPanel, NULL);
	message = XtVaCreateManagedWidget("Play Panex!",
		xmLabelWidgetClass, messageRowCol, NULL);
	puzzle = XtVaCreateManagedWidget("panex",
		panexWidgetClass, mainPanel, NULL);
#elif defined(HAVE_ATHENA)
	XtAppAddActions(appCon, actions, XtNumber(actions));
	createClearQuery((char *) "Clear Query",
		(char *) "Are you sure you want to start over?");
	createHelp(&descriptionDialog, (char *) "Description",
		(char *) descriptionHelp);
	createScrollHelp(&featuresDialog, (char *) "Features",
		(char *) featuresHelp);
	createHelp(&synopsisDialog, (char *) "Synopsis",
		(char *) synopsisHelp);
	createHelp(&referencesDialog, (char *) "References",
		(char *) referencesHelp);
	createHelp(&aboutDialog, (char *) "About",
		(char *) aboutHelp);
	mainPanel = XtVaCreateManagedWidget("form",
		panedWidgetClass, topLevel, NULL);
	menuBar = XtVaCreateManagedWidget("MenuBar",
		formWidgetClass, mainPanel,
		XtNborderWidth, 1, NULL);
	fileLabel = XtVaCreateManagedWidget("File",
		menuButtonWidgetClass, menuBar,
		XtNborderWidth, 0, NULL);
	fileMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, fileLabel, NULL);
	for (i = 0; i < numFileTypes; i++) {
		w = XtVaCreateManagedWidget(fileTypes[i],
			smeBSBObjectClass, fileMenu, NULL);
		if (i < numFileTypes - 1 &&
				strcmp(fileTypes[i + 1], "Exit") == 0)
			XtVaCreateManagedWidget("menuButton",
				smeLineObjectClass, fileMenu, NULL);
		XtAddCallback(w,
			XtNcallback, (XtCallbackProc) fileMenuListener,
			(XtPointer) (size_t) i);
	}
	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) playMenuListener,
			(XtPointer) (size_t) i);
	}
	helpLabel = XtVaCreateManagedWidget("Help",
		menuButtonWidgetClass, menuBar,
		XtNborderWidth, 0,
		XtNfromHoriz, playLabel, NULL);
	helpMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, helpLabel, NULL);
	for (i = 0; i < numHelpTypes; i++) {
		w = XtVaCreateManagedWidget(helpTypes[i],
			smeBSBObjectClass, helpMenu, NULL);
		XtAddCallback(w,
			XtNcallback, (XtCallbackProc) helpMenuListener,
			(XtPointer) (size_t) i);
	}
	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, 64,
		XtNfromHoriz, recordLabel, NULL);
	radioRowCol = XtVaCreateManagedWidget("radioRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, movesRowCol, NULL);
	tileChanger = XtVaCreateManagedWidget("tileChanger",
		boxWidgetClass, radioRowCol,
		XtNborderWidth, 0, NULL);
	modeMenu = XtVaCreateManagedWidget("modeMenu",
		formWidgetClass, radioRowCol,
		XtNborderWidth, 0,
		XtNfromHoriz, tileChanger, NULL);
	changerRowCol = XtVaCreateManagedWidget("changerRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, radioRowCol, NULL);
	speedChanger = XtVaCreateManagedWidget("speedChanger",
		boxWidgetClass, changerRowCol,
		XtNborderWidth, 0, NULL);
	message = XtVaCreateManagedWidget("Welcome",
		labelWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNwidth, 128,
		XtNfromVert, changerRowCol, NULL);
	puzzle = XtVaCreateManagedWidget("panex",
		panexWidgetClass, mainPanel,
		XtNfromVert, controlPanel, NULL);
	createTileSlider(tileChanger);
	createSpeedSlider(speedChanger);
	XtVaGetValues(puzzle,
		XtNmode, &mode, NULL);
	createModePopupMenu(modeMenu, modeString, mode,
		sizeof(modeString) / sizeof(*modeString));
	XtRealizeWidget(topLevel);
	if (wmDeleteWindow == None)
		wmDeleteWindow = XInternAtom(XtDisplay(topLevel),
			"WM_DELETE_WINDOW", FALSE);
	XSetWMProtocols(XtDisplay(topLevel), XtWindow(topLevel),
		&wmDeleteWindow, 1);
#else
	puzzle = XtVaCreateManagedWidget("panex",
		panexWidgetClass, 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) puzzleListener, (XtPointer) NULL);
#ifdef HAVE_MOTIF
	updateToggle(playMenu, &soundMenuItem,
		False,
#ifdef EXTRA
		9,
#else
		5,
#endif
		(XtCallbackProc) soundToggle);
	descriptionDialog = createHelp(menuBar, (char *) "Description",
		(char *) descriptionHelp);
	featuresDialog = createScrollHelp(menuBar, (char *) "Features",
		(char *) featuresHelp);
	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
