/*
 * @(#)xmball.c
 *
 * Copyright 1994 - 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 Mball */

#ifndef WINVER
#include "version.h"
static const char aboutHelp[] = {
"Mball 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"
"[-wedge{0|1|2|3|4|5|6|7|8|9|10|11} {color}]\n"
"[-{border|bd} {color}] [-delay msecs] [-[no]sound]\n"
"[-moveSound {filename}] [-{font|fn} {fontname}]\n"
"[-[no]perspective] [-wedges {int}] [-band {int}]\n"
"[-[no]orient] [-[no]practice] [-base {int}]\n"
"[-userName {string}] [-scoreFile {filename}]\n"
"[-scores] [-version]"
};
#endif

#if defined(HAVE_MOTIF) || defined(WINVER)
static const char descriptionHelp[] = {
"The original puzzle has 8 wedges on a sphere "
"(longitudinal cuts), with each wedge divided into 4 bands\n"
"(latitudinal cuts).  There are essentially 3 varieties: "
"Geomaster 8 colors (beachball, default colors in this\n"
"puzzle), Duomaster 2 colors (black and white beachball), "
"and a variety of picture Masterballs.  For some reason,\n"
"they say the Geomaster is easier than the Duomaster.  The "
"picture Masterballs are the hardest since all the pieces\n"
"have a set solved position.  On the Duo and Geo Masterballs "
"pieces could be swapped or in a different order and still\n"
"be in a solved position.  Zurick University's Mathematics "
"faculty has calculated that Masterball's 32 segments can\n"
"be arranged in 355,682,548,566,633,480,192,000,000 different "
"possible combinations.  Masterball was invented by Dr.\n"
"Geza Gyovai Hungarian Engineer, manufactured by Whole "
"Systems Design, Inc..\n"
"\n"
"Related puzzles are the Saturn Puzzle, an oriented Masterball "
"with one band, and the harder Hockey Puck Puzzle, like\n"
"the Saturn Puzzle but with 12 wedges instead of 8."
};

static const char featuresHelp[] = {
"Press \"mouse-left\" button to move a piece.  Release "
"\"mouse-left\" button on a piece and the pieces will\n"
"then turn towards where the mouse button was released.  "
"Usually, a click and release on the same wedge is\n"
"ambiguous and the puzzle will not turn.  If there is "
"some ambiguity between a turn or a flip, a flip is\n"
"assumed, except if shift key is used.\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"
"\"S\" or \"s\" keys to start auto-solver.  Only works for "
"wedges <= 8 or 1 band.\n"
"\n"
"Press \"O\" or \"o\" keys to toggle the orient mode.  One "
"has to orient the wedges in orient mode, besides\n"
"getting all the wedges to be the same color.  To do this "
"one has to get the numbers to be on the same side\n"
"of the ball in clockwise order.\n"
"\n"
"Press \"I\" or \"i\" keys to increase the number of bands.\n"
"\n"
"Press \"D\" or \"d\" keys to decrease the number of bands.\n"
"\n"
"Press \"2\", \"4\", \"6\", \"8\", \"0\", or \"=\" keys "
"(not the keypad 2, 4, 6, 8, 0) to change to 2, 4, 6, 8, 10,\n"
"or 12 wedges, respectively.  Note: if there were odd "
"number of wedges, there would be no 180 degree turn\n"
"and therefore the puzzle would be inoperable.\n"
"\n"
"Press \"T\" or \"t\" keys to toggle the perspective look.\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 Master Ball as:\n"
"N / *  Upper Upper Left, Counterclockwise, Upper Upper Right\n"
"7 8 9  Up, Upper Right\n"
"  ^\n"
"4<5>6  Left, Clockwise, Right\n"
"  v\n"
"1 2 3  Lower Left, Down, Lower Right\n"
" 0  .  Lower Lower Left, Lower Lower Right\n"
"\n"
"Use the control key and the left mouse button, keypad, "
"or arrow keys to move the whole Masterball.  This\n"
"is not recorded as a turn."
};

static const char referencesHelp[] = {
"http://wsd.com/masterball"
};
#endif

static const char solveHelp[] = {
"Auto-solver: sorry, only implemented for wedges <= 8 or 1 band."
};

#include "file.h"
#ifdef WINVER
#include "MballP.h"
#define TITLE "wmball"

static MballRec 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->mball.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_XAW3D)
#include <X11/Xaw3d/Form.h>
#include <X11/Xaw3d/Label.h>
#include <X11/Xaw3d/Toggle.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/Command.h>
#include <X11/Xaw3d/Dialog.h>
#define HAVE_ATHENA 1
#elif defined(HAVE_ATHENA)
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Toggle.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/Command.h>
#include <X11/Xaw/Dialog.h>
#endif
#define SET_STARTED(w,b) XtVaSetValues(w, XtNstart, b, NULL)
#include "Mball.h"
#ifdef HAVE_XPM
#include <X11/xpm.h>
#ifdef CONSTPIXMAPS
#include "16x16/mball.xpm"
#include "22x22/mball.xpm"
#include "24x24/mball.xpm"
#include "32x32/mball.xpm"
#include "48x48/mball.xpm"
#include "64x64/mball.xpm"
#else
#include "pixmaps/16x16/mball.xpm"
#include "pixmaps/22x22/mball.xpm"
#include "pixmaps/24x24/mball.xpm"
#include "pixmaps/32x32/mball.xpm"
#include "pixmaps/48x48/mball.xpm"
#include "pixmaps/64x64/mball.xpm"
#endif
#define RESIZE_XPM(s) ((char **) (((s)<=32)?\
(((s)<=22)?(((s)<=16)?mball_16x16:mball_22x22):\
(((s)<=24)?mball_24x24:mball_32x32)):\
(((s)<=48)?mball_48x48:mball_64x64)))
#endif
#include "pixmaps/64x64/mball.xbm"
#define DEFINE_XBM (char *) mball_64x64_bits, mball_64x64_width, mball_64x64_height
#ifndef SCOREPATH
#ifdef VMS
#define SCOREPATH "SYS$LOGIN:"
#else
#define SCOREPATH "/var/games/xpuzzles"
#endif
#endif
#endif

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

#define MAX_BANDS 6
#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[2][(MAX_WEDGES - MIN_WEDGES) / 2 + 1]
[MAX_BANDS - MIN_BANDS + 1];
static int movesDsp = 0;
static char messageDsp[MESSAGE_LENGTH] =
	"Welcome                        ";
static char recordDsp[MESSAGE_LENGTH] = "NOT RECORDED";

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
static Widget movesText, recordText, message;
static Widget bandChanger;
static Widget speedChanger;
static Widget orientizeSwitch, practiceSwitch;
static Widget practiceDialog, randomizeDialog, solveDialog;
static char buff[MESSAGE_LENGTH];
#define PRINT_MESSAGE(b) printState(message, b)
#define MIN_SPEED 1
#define MAX_SPEED 50
static const char *wedgeString[] =
{
	"Two", "Four", "Six", "Eight", "Ten", "Twelve"
};
#elif !defined(WINVER)
#define PRINT_MESSAGE(b) XtWarning(b)
#endif
#ifdef HAVE_MOTIF
#define SCROLL_SIZE 30 /* A page */
static Widget wedge[(MAX_WEDGES - MIN_WEDGES) / 2 + 1];
static Widget descriptionDialog, featuresDialog;
static Widget synopsisDialog, referencesDialog, aboutDialog;
static Arg args[10];
#elif defined(HAVE_ATHENA)
#define SCROLL_SIZE 100
static Widget wedgeLabel, wedgeMenu;
static Widget bandSliderLabel, speedSliderLabel;
static Widget practiceOKDialog, practiceCancelDialog;
static Widget randomizeOKDialog, randomizeCancelDialog;
static Widget solveOKDialog;
#else
static char titleDsp[TITLE_LENGTH] = "";
#endif
static char scoreFileName[FILE_NAME_LENGTH] = SCOREFILE;
static char fileName[FILE_NAME_LENGTH];
static Boolean randomized = False;
#ifdef WINVER
#define PROGRAM_NAME_LENGTH 80
static char progDsp[PROGRAM_NAME_LENGTH] = TITLE;
static char userNameDsp[USER_NAME_LENGTH] = "guest";
#else
static Pixmap pixmap = None;
static Widget topLevel, puzzle;
static char *progDsp;
static char userNameDsp[USER_NAME_LENGTH] = "";

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

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

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, j, orient;

	(void) printf("              MBALL  HALL OF FAME\n\n");
	(void) printf("ORIENT WEDGE BAND USER            MOVES\n");
	for (orient = 0; orient < 2; orient++)
		for (i = 0; i < (MAX_WEDGES - MIN_WEDGES) / 2 + 1; i++)
			for (j = 0; j < MAX_BANDS - MIN_BANDS + 1; j++) {
				if (puzzleRecord[orient][i][j].score > 0)
					(void) printf("%6d%6d%5d %-12s%9d\n",
						orient, 2 * i + 1, j + 1,
						puzzleRecord[orient][i][j].name,
						puzzleRecord[orient][i][j].score);
			}
}
#endif

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

	for (orient = 0; orient < 2; orient++)
		for (i = 0; i < (MAX_WEDGES - MIN_WEDGES) / 2 + 1; i++)
			for (j = 0; j < MAX_BANDS - MIN_BANDS + 1; j++) {
				puzzleRecord[orient][i][j].score = NEVER;
				(void) strncpy(puzzleRecord[orient][i][j].name,
					NOACCESS, USER_NAME_LENGTH);
			}
}

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

static void
writeRecords(void)
{
	FILE *fp;
	int i, j, 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 (orient = 0; orient < 2; orient++) {
			for (i = 0; i < (MAX_WEDGES - MIN_WEDGES) / 2 + 1; i++) {
				for (j = 0; j < MAX_BANDS - MIN_BANDS + 1; j++)
					(void) fprintf(fp, "%d %s\n",
						puzzleRecord[orient][i][j].score,
						puzzleRecord[orient][i][j].name);
				if ((MAX_WEDGES - MIN_WEDGES) / 2 ||
						orient < 1)
					(void) fprintf(fp, "\n");
			}
			if (orient < 1)
				(void) fprintf(fp, "\n");
		}
#if HAVE_FCNTL_H
		(void) close(lfd);
		(void) unlink(lockFile);
#endif
		(void) fclose(fp);
	}
}

static void
printRecord(int wedges, int bands, Boolean orient, Boolean practice)
{
	int i = (orient) ? 1 : 0;
	int w = (wedges - MIN_WEDGES) / 2;
	int r = bands - MIN_BANDS;

	if (practice) {
		(void) strncpy(recordDsp, "practice", 9);
	} else if (bands > MAX_BANDS) {
		(void) strncpy(recordDsp, "NOT RECORDED", 13);
	} else if (puzzleRecord[i][w][r].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][w][r].score,
			puzzleRecord[i][w][r].name);
#else
		(void) sprintf(recordDsp, "%d %s",
			puzzleRecord[i][w][r].score,
			puzzleRecord[i][w][r].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 wedges, int bands
#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.%d: %d@ (%d/%s) - %s",
		progDsp, wedges, bands, nMoves, recordDsp, msg);
#else
	(void) sprintf(titleDsp,
		"%s.%d: %d@ (%d/%s) - %s",
		progDsp, wedges, bands, 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 wedges, int bands, Boolean orient)
{
	int i = (orient) ? 1 : 0;
	int w = (wedges - MIN_WEDGES) / 2;
	int r = bands - MIN_BANDS;

	if (bands <= MAX_BANDS && (counter < puzzleRecord[i][w][r].score ||
			puzzleRecord[i][w][r].score <= NEVER)) {
		readRecords();	/* Maybe its been updated by another */
		puzzleRecord[i][w][r].score = counter;
		(void) strncpy(puzzleRecord[i][w][r].name, userNameDsp,
			USER_NAME_LENGTH);
		if (orient && (counter < puzzleRecord[!i][w][r].score ||
				puzzleRecord[!i][w][r].score <= NEVER)) {
			puzzleRecord[!i][w][r].score = counter;
			(void) strncpy(puzzleRecord[!i][w][r].name, userNameDsp,
				USER_NAME_LENGTH);
		}
		writeRecords();
		printRecord(wedges, bands, orient, False);
		return True;
	}
	return False;
}

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
static void
orientToggle(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
		XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		int state
#endif
		)
{
	int wedges, bands;
	Boolean orient, practice, old;

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

static void
practiceToggle(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
		XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		int state
#endif
		)
{
	int wedges, bands;
	Boolean orient, practice, old;

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

static void
wedgeListener(Widget w,
#ifdef HAVE_MOTIF
		int position, XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
		XtPointer clientData, XtPointer callData
#endif
		)
{
	int wedges, bands, old;
	Boolean orient, practice;

#ifdef HAVE_MOTIF
	if (!cbs->set)
		return;
#elif defined(HAVE_ATHENA)
	int position = (size_t) clientData;
#endif
	XtVaGetValues(puzzle,
		XtNwedges, &old,
		XtNbands, &bands,
		XtNorient, &orient,
		XtNpractice, &practice, NULL);
	wedges = position * 2 + MIN_WEDGES;
	if (old == wedges)
		return;
#ifdef HAVE_ATHENA
	XtVaSetValues(wedgeLabel,
		XtNlabel, wedgeString[position], NULL);
#endif
	XtVaSetValues(puzzle,
		XtNwedges, wedges, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(wedges, bands, orient, practice);
	messageDsp[0] = '\0';
	printState(message, messageDsp);
}

static void
bandChangeListener(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
#elif defined(HAVE_ATHENA)
		XtPointer callData
#endif
		)
{
	int wedges, bands, old;
	Boolean orient, practice;

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	int limit;
	bands = cbs->position;
#else
	bands = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	long position = (size_t) callData; /* left or right button */
	bands = ABS(position) *
		(MAX_BANDS - MIN_BANDS) / SCROLL_SIZE + 1;
	if (bands < MIN_BANDS || bands > MAX_BANDS) {
		/* middle button */
		bands = (int) (*((float *) callData) *
			(MAX_BANDS - MIN_BANDS)) + MIN_BANDS;
		if (bands < MIN_BANDS || bands > MAX_BANDS)
			return;
	}
#endif
	XtVaGetValues(puzzle,
		XtNwedges, &wedges,
		XtNbands, &old,
		XtNorient, &orient,
		XtNpractice, &practice, NULL);
	if (old == bands)
		return;
#if defined(HAVE_MOTIF) && defined(USE_SPIN)
	XtVaGetValues(bandChanger,
		XmNmaximumValue, &limit, NULL);
	if (bands >= limit)
		XtVaSetValues(bandChanger,
			XmNmaximumValue, bands + 1,
			XmNposition, bands, NULL);
#elif defined(HAVE_ATHENA)
	(void) sprintf(buff, "Bands: %d", bands);
	XtVaSetValues(label,
		XtNlabel, buff, NULL);
#endif
	XtVaSetValues(puzzle,
		XtNbands, bands, NULL);
	movesDsp = 0;
	intCpy(buff, movesDsp);
	printState(movesText, buff);
	printRecord(wedges, bands, orient, practice);
	messageDsp[0] = '\0';
	printState(message, messageDsp);
}

static void
speedChangeListener(Widget widget, XtPointer clientData,
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
#elif defined(HAVE_ATHENA)
		XtPointer callData
#endif
		)
{
	int speed, delay, 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 + 1;
	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;
	}
	(void) sprintf(buff, "Animation Speed: %d", speed);
	XtVaSetValues(label,
		XtNlabel, buff, NULL);
#endif
	delay = MAX_SPEED + MIN_SPEED - speed - 1;
	XtVaGetValues(puzzle,
		XtNdelay, &old, NULL);
	if (old != delay) {
		XtVaSetValues(puzzle,
			XtNdelay, delay, NULL);
	}
}

static void
puzzlePracticeListener(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)
	XtUnmanageChild(practiceDialog);
#endif
		XtVaSetValues(puzzle,
			XtNmenu, ACTION_PRACTICE, NULL);
}

static void
puzzleRandomizeListener(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)
	XtUnmanageChild(randomizeDialog);
#endif
		XtVaSetValues(puzzle,
			XtNmenu, ACTION_RANDOMIZE, NULL);
}
#endif

static void
initialize(
#ifdef WINVER
MballWidget w, HBRUSH brush
#else
Widget w
#endif
)
{
	int wedges, bands;
	Boolean orient, practice;
	char *userName, *scoreFile;

#ifdef WINVER
	initializePuzzle(w, brush);

	wedges = w->mball.wedges;
	bands = w->mball.bands;
	orient = w->mball.orient;
	practice = w->mball.practice;
	userName = w->mball.userName;
	scoreFile = w->mball.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,
		XtNwedges, &wedges,
		XtNbands, &bands,
		XtNorient, &orient,
		XtNpractice, &practice,
		XtNscoreOnly, &scoreOnly,
		XtNversionOnly, &versionOnly, NULL);
	if (versionOnly) {
		(void) printf("%s\n", aboutHelp);
		exit(0);
	}
	if (strcmp(scoreFile, "")) {
		(void) strncpy(scoreFileName, scoreFile, FILE_NAME_LENGTH - 1);
		scoreFileName[FILE_NAME_LENGTH - 1] = '\0';
	}
	if (scoreOnly) {
		initRecords();
		readRecords();
		printRecords();
		exit(0);
	}
#ifdef HAVE_MOTIF
	{
		int delay, speed;

#ifdef USE_SPIN
		int limit;
		XtVaGetValues(bandChanger,
			XmNmaximumValue, &limit, NULL);
		if (bands >= limit)
			XtVaSetValues(bandChanger,
				XmNposition, bands,
				XmNmaximumValue, bands + 1, NULL);
		else
			XtVaSetValues(bandChanger,
				XmNposition, bands, NULL);
#else
		if (bands > MAX_BANDS)
			XtVaSetValues(bandChanger,
				XmNmaximum, bands, NULL);
		XmScaleSetValue(bandChanger, bands);
#endif
		XmToggleButtonSetState(wedge[(wedges - MIN_WEDGES) / 2], True, False);
		XmToggleButtonSetState(orientizeSwitch, orient, True);
		XmToggleButtonSetState(practiceSwitch, practice, True);
		XtVaGetValues(w, XtNdelay, &delay, NULL);
		speed = MAX_SPEED + MIN_SPEED - delay - 1;
#ifdef USE_SPIN
		XtVaSetValues(speedChanger,
			XmNposition, speed, NULL);
#else
		XmScaleSetValue(speedChanger, speed);
#endif
	}
#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(wedges, bands, orient, practice);
	printStatus(messageDsp, movesDsp
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		, wedges, bands
#endif
		);
}

#ifdef WINVER
void
setPuzzle(MballWidget w, int reason)
#else
static void
puzzleListener(Widget w, caddr_t clientData,
		mballCallbackStruct *callData)
#endif
{
#ifndef WINVER
	int reason = callData->reason;
	Boolean start;
#endif
#if defined(HAVE_MOTIF) && defined(USE_SPIN)
	int limit;
#elif defined(HAVE_ATHENA)
	int max;
	char *defaultString;
#endif
	int wedgesIndex, wedges, bands;
	Boolean orient, practice, cheat, perspective;

	messageDsp[0] = '\0';
#ifdef WINVER
	wedges = w->mball.wedges;
	bands = w->mball.bands;
	orient = w->mball.orient;
	practice = w->mball.practice;
	cheat = w->mball.cheat;
	/*start = w->mball.started;*/
	perspective = w->mball.perspective;
#else
	XtVaGetValues(w,
		XtNwedges, &wedges,
		XtNbands, &bands,
		XtNorient, &orient,
		XtNpractice, &practice,
		XtNstart, &start,
		XtNcheat, &cheat,
		XtNperspective, &perspective, NULL);
#endif
	switch (reason) {
	case ACTION_HIDE:
#ifdef WINVER
		ShowWindow(w->core.hWnd, SW_SHOWMINIMIZED);
#else
		(void) XIconifyWindow(XtDisplay(topLevel),
			XtWindow(topLevel),
			XScreenNumberOfScreen(XtScreen(topLevel)));
#endif
		break;
#ifndef WINVER
	case ACTION_PRACTICE_QUERY:
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
		XtManageChild(practiceDialog);
#else
		XtVaSetValues(puzzle, XtNmenu, ACTION_PRACTICE, NULL);
#endif
		break;
	case ACTION_RANDOMIZE_QUERY:
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
		XtManageChild(randomizeDialog);
#else
		XtVaSetValues(puzzle, XtNmenu, ACTION_RANDOMIZE, NULL);
#endif
		break;
#endif
	case ACTION_SOLVE_MESSAGE:
#ifdef WINVER
		(void) MessageBox(w->core.hWnd, solveHelp,
			"Auto-solve", MB_OK);
#else
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
		XtManageChild(solveDialog);
#else
		(void) strncpy(messageDsp, solveHelp, MESSAGE_LENGTH);
#endif
#endif
		break;
	case ACTION_RESTORE:
		if (practice) {
			(void) strncpy(recordDsp, "practice",
				MESSAGE_LENGTH);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
			printState(recordText, recordDsp);
#endif
		}
		movesDsp = 0;
		randomized = False;
		break;
	case ACTION_RESET:
		movesDsp = 0;
		randomized = False;
		break;
	case ACTION_AMBIGUOUS:
		(void) strncpy(messageDsp, "Ambiguous move",
			MESSAGE_LENGTH);
		break;
	case ACTION_ILLEGAL:
		if (practice || randomized)
			(void) strncpy(messageDsp, "Illegal move",
				MESSAGE_LENGTH);
		else
			(void) strncpy(messageDsp,
				"Randomize (z) to start", MESSAGE_LENGTH);
		break;
	case ACTION_MOVED:
		movesDsp++;
		SET_STARTED(w, True);
		break;
	case ACTION_CONTROL:
		return;
	case ACTION_SOLVED:
		if (practice) {
			movesDsp = 0;
		} else if (cheat) {
			(void) strncpy(messageDsp, "No cheating ", 13);
			(void) strncat(messageDsp, userNameDsp, USER_NAME_LENGTH);
			(void) strncat(messageDsp, "!!", 3);
		} else if (handleSolved(movesDsp, wedges, bands, orient)) {
			(void) strncpy(messageDsp, "Congratulations ", 17);
			(void) strncat(messageDsp, userNameDsp, USER_NAME_LENGTH);
			(void) strncat(messageDsp, "!!", 3);
		} else {
			(void) strncpy(messageDsp, "Solved!", 8);
		}
		SET_STARTED(w, False);
		randomized = False;
		break;
	case ACTION_COMPUTED:
		SET_STARTED(w, False);
		break;
	case ACTION_PRACTICE:
		movesDsp = 0;
		randomized = False;
		practice = !practice;
		if (!practice)
			(void) strncpy(messageDsp, "Randomize (z) to start",
				MESSAGE_LENGTH);
		printRecord(wedges, bands, orient, practice);
#ifdef WINVER
		w->mball.practice = practice;
		w->mball.started = False;
#else
		XtVaSetValues(w,
			XtNpractice, practice,
			XtNstart, False, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(practiceSwitch, practice, True);
#elif defined(HAVE_ATHENA)
		XtVaSetValues(practiceSwitch, XtNstate, practice, NULL);
#endif
#endif
		break;
	case ACTION_RANDOMIZE:
		movesDsp = 0;
		randomized = True;
#ifdef WINVER
		w->mball.practice = False;
		w->mball.started = False;
#else
		XtVaSetValues(w,
			XtNpractice, False,
			XtNstart, False, NULL);
#endif
		break;
	case ACTION_INCREMENT:
		movesDsp = 0;
		bands++;
		printRecord(wedges, bands, orient, practice);
#ifdef WINVER
		w->mball.bands = bands;
#else
		XtVaSetValues(w, XtNbands, bands, NULL);
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		XtVaGetValues(bandChanger,
			XmNmaximumValue, &limit, NULL);
		if (bands >= limit)
			XtVaSetValues(bandChanger,
				XmNmaximumValue, bands + 1,
				XmNposition, bands, NULL);
		else
			XtVaSetValues(bandChanger,
				XmNposition, bands, NULL);
#else
		if (bands > MAX_BANDS)
			XtVaSetValues(bandChanger,
				XmNmaximum, bands, NULL);
		XmScaleSetValue(bandChanger, bands);
#endif
#elif defined(HAVE_ATHENA)
		(void) sprintf(buff, "Bands: %d", bands);
		XtVaSetValues(bandSliderLabel,
			XtNlabel, buff, NULL);
#endif
#endif
		break;
	case ACTION_DECREMENT:
		movesDsp = 0;
		bands--;
		printRecord(wedges, bands, orient, practice);
#ifdef WINVER
		w->mball.bands = bands;
#else
		XtVaSetValues(w, XtNbands, bands, NULL);
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
		if (bands > MAX_BANDS)
			XtVaSetValues(bandChanger,
				XmNmaximumValue, bands,
				XmNposition, bands, NULL);
		else
			XtVaSetValues(bandChanger,
				XmNposition, bands, NULL);
#else
		if (bands > MAX_BANDS)
			XtVaSetValues(bandChanger,
				XmNmaximum, bands, NULL);
		XmScaleSetValue(bandChanger, bands);
#endif
#elif defined(HAVE_ATHENA)
		(void) sprintf(buff, "Bands: %d", bands);
		XtVaSetValues(bandSliderLabel,
			XtNlabel, buff, NULL);
#endif
#endif
		break;
	case ACTION_ORIENTIZE:
		movesDsp = 0;
		orient = !orient;
		printRecord(wedges, bands, orient, practice);
#ifdef WINVER
		w->mball.orient = orient;
#else
		XtVaSetValues(w, XtNorient, orient, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(orientizeSwitch, orient, True);
#elif defined(HAVE_ATHENA)
		XtVaSetValues(orientizeSwitch, XtNstate, orient, NULL);
#endif
#endif
		break;
	case ACTION_WEDGE2:
	case ACTION_WEDGE4:
	case ACTION_WEDGE6:
	case ACTION_WEDGE8:
	case ACTION_WEDGE10:
	case ACTION_WEDGE12:
		movesDsp = 0;
		wedgesIndex = reason - ACTION_WEDGE2;
		wedges = 2 * wedgesIndex + MIN_WEDGES;
		printRecord(wedges, bands, orient, practice);
#ifdef WINVER
		w->mball.wedges = wedges;
#else
		XtVaSetValues(w, XtNwedges, wedges, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(wedge[wedgesIndex],
			True, True);
#elif defined(HAVE_ATHENA)
		max = findMaxLength((char **) wedgeString,
			sizeof(wedgeString) / sizeof(*wedgeString));
		createBlank(&defaultString, max, (char *) wedgeString[wedgesIndex], 0);
		XtVaSetValues(wedgeLabel,
			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)
			(void) sprintf(buff, "Animation Speed: %d", speed);
			XtVaSetValues(speedSliderLabel,
				XtNlabel, buff, NULL);
#endif
		}
		return;
#endif
	case ACTION_PERSPECTIVE:
		perspective = !perspective;
#ifdef WINVER
		w->mball.perspective = perspective;
#else
		XtVaSetValues(w, XtNperspective, perspective, NULL);
#endif
		break;
	}
	printStatus(messageDsp, movesDsp
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		, wedges, bands
#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;

	widget.core.hWnd = hWnd;
	if (GetFocus()) {
		if (!widget.mball.focus) {
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_BRUSH));
			enterPuzzle(&widget);
			(void) EndPaint(hWnd, &paint);
		}
	} else {
		if (widget.mball.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_CONTROL) >> 1) ? 1 : 0);
			} else if (zDelta < -(WHEEL_DELTA >> 1)) {
				movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					BOTTOM,
					(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0);
			}
		}
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
#endif
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case ACTION_GET:
			getPuzzle(&widget);
			resizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_WRITE:
			writePuzzle(&widget);
			break;
		case ACTION_EXIT:
			destroyPuzzle(brush);
			break;
		case ACTION_HIDE:
			hidePuzzle(&widget);
			break;
		case ACTION_UNDO:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			undoPuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_REDO:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			redoPuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_CLEAR:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			clearPuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_PRACTICE:
			practicePuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_RANDOMIZE:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			randomizePuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_SOLVE:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			solvePuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_ORIENTIZE:
			orientizePuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_PERSPECTIVE:
			perspectivePuzzle(&widget);
			resizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_TOP:
		case ACTION_TTR:
		case ACTION_TR:
		case ACTION_RIGHT:
		case ACTION_BR:
		case ACTION_BBR:
		case ACTION_BOTTOM:
		case ACTION_BBL:
		case ACTION_BL:
		case ACTION_LEFT:
		case ACTION_TL:
		case ACTION_TTL:
		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);
				(void) movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					(int) LOWORD(wParam) - ACTION_TOP, FALSE);
				(void) ReleaseDC(hWnd, widget.core.hDC);
			}
			break;
		case ACTION_CONTROL_TOP:
		case ACTION_CONTROL_TTR:
		case ACTION_CONTROL_TR:
		case ACTION_CONTROL_RIGHT:
		case ACTION_CONTROL_BR:
		case ACTION_CONTROL_BBR:
		case ACTION_CONTROL_BOTTOM:
		case ACTION_CONTROL_BBL:
		case ACTION_CONTROL_BL:
		case ACTION_CONTROL_LEFT:
		case ACTION_CONTROL_TL:
		case ACTION_CONTROL_TTL:
		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);
				(void) movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					(int) LOWORD(wParam) - ACTION_CONTROL_TOP,
					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_WEDGE2:
		case ACTION_WEDGE4:
		case ACTION_WEDGE6:
		case ACTION_WEDGE8:
		case ACTION_WEDGE10:
		case ACTION_WEDGE12:
			wedgePuzzle(&widget,
				(int) LOWORD(wParam) - ACTION_WEDGE2);
			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 *) "*mball.mono", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nomono", (char *) "*mball.mono", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-rv", (char *) "*mball.reverseVideo", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-reverse", (char *) "*mball.reverseVideo", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-norv", (char *) "*mball.reverseVideo", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-noreverse", (char *) "*mball.reverseVideo", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-fg", (char *) "*mball.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-foreground", (char *) "*mball.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-bg", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-background", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-bd", (char *) "*mball.pieceBorder", XrmoptionSepArg, NULL},
	{(char *) "-border", (char *) "*mball.pieceBorder", XrmoptionSepArg, NULL},
	{(char *) "-wedge0", (char *) "*mball.wedgeColor0", XrmoptionSepArg, NULL},
	{(char *) "-wedge1", (char *) "*mball.wedgeColor1", XrmoptionSepArg, NULL},
	{(char *) "-wedge2", (char *) "*mball.wedgeColor2", XrmoptionSepArg, NULL},
	{(char *) "-wedge3", (char *) "*mball.wedgeColor3", XrmoptionSepArg, NULL},
	{(char *) "-wedge4", (char *) "*mball.wedgeColor4", XrmoptionSepArg, NULL},
	{(char *) "-wedge5", (char *) "*mball.wedgeColor5", XrmoptionSepArg, NULL},
	{(char *) "-wedge6", (char *) "*mball.wedgeColor6", XrmoptionSepArg, NULL},
	{(char *) "-wedge7", (char *) "*mball.wedgeColor7", XrmoptionSepArg, NULL},
	{(char *) "-wedge8", (char *) "*mball.wedgeColor8", XrmoptionSepArg, NULL},
	{(char *) "-wedge9", (char *) "*mball.wedgeColor9", XrmoptionSepArg, NULL},
	{(char *) "-wedge10", (char *) "*mball.wedgeColor10", XrmoptionSepArg, NULL},
	{(char *) "-wedge11", (char *) "*mball.wedgeColor11", XrmoptionSepArg, NULL},
	{(char *) "-delay", (char *) "*mball.delay", XrmoptionSepArg, NULL},
	{(char *) "-sound", (char *) "*mball.sound", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nosound", (char *) "*mball.sound", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-fn", (char *) "*mball.font", XrmoptionSepArg, NULL},
	{(char *) "-font", (char *) "*mball.font", XrmoptionSepArg, NULL},
	{(char *) "-perspective", (char *) "*mball.perspective", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-noperspective", (char *) "*mball.perspective", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-wedges", (char *) "*mball.wedges", XrmoptionSepArg, NULL},
	{(char *) "-bands", (char *) "*mball.bands", XrmoptionSepArg, NULL},
	{(char *) "-orient", (char *) "*mball.orient", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-noorient", (char *) "*mball.orient", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-practice", (char *) "*mball.practice", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nopractice", (char *) "*mball.practice", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-base", (char *) "*mball.base", XrmoptionSepArg, NULL},
	{(char *) "-userName", (char *) "*mball.userName", XrmoptionSepArg, NULL},
	{(char *) "-scoreFile", (char *) "*mball.scoreFile", XrmoptionSepArg, NULL},
	{(char *) "-scores", (char *) "*mball.scoreOnly", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-version", (char *) "*mball.versionOnly", XrmoptionNoArg, (char *) "TRUE"}
};

#ifdef HAVE_MOTIF
static void
fileMenuListener(Widget w, XtPointer value, XtPointer clientData)
{
	int val = (int) ((size_t) value) + ACTION_GET;

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

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

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

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

	messageString = XmStringCreateLtoR(text, charSet);
#ifdef HAVE_SNPRINTF
	(void) snprintf(titleDsp, TITLE_FILE_LENGTH,
		"%s: %s", progDsp, title);
#else
	(void) sprintf(titleDsp, "%s: %s", progDsp, title);
#endif
	titleString = XmStringCreateSimple((char *) titleDsp);
	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 *text, char *title)
{
	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);
#ifdef HAVE_SNPRINTF
	(void) snprintf(titleDsp, TITLE_FILE_LENGTH,
		"%s: %s", progDsp, title);
#else
	(void) sprintf(titleDsp, "%s: %s", progDsp, title);
#endif
	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 *text, char *title)
{
	Widget dialog, pane, scrolledText, form, label, button;
	int n = 0;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL;

#ifdef HAVE_SNPRINTF
	(void) snprintf(titleDsp, TITLE_FILE_LENGTH,
		"%s: %s", progDsp, title);
#else
	(void) sprintf(titleDsp, "%s: %s", progDsp, title);
#endif
	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;
}

static void
helpMenuListener(Widget w, XtPointer value, XtPointer clientData)
{
	int val = (int) ((size_t) value);

	switch (val) {
	case 0:
		XtManageChild(descriptionDialog);
		break;
	case 1:
		XtManageChild(featuresDialog);
		break;
	case 2:
		XtManageChild(synopsisDialog);
		break;
	case 3:
		XtManageChild(referencesDialog);
		break;
	case 4:
		XtManageChild(aboutDialog);
		break;
	default:
		{
			char *buf;

			intCat(&buf, "helpMenuListener: %d", val);
			XtWarning(buf);
			free(buf);
		}
	}
}

#elif defined(HAVE_ATHENA)
static void
createBandSlider(Widget form)
{
	Widget scrollbar;
	char message[BUFSIZ];
	int bands;

	XtVaGetValues(puzzle,
		XtNbands, &bands, NULL);
	(void) sprintf(message, "Bands: %d", bands);
	scrollbar = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	bandSliderLabel = XtVaCreateManagedWidget(message,
		labelWidgetClass, form,
		XtNfromHoriz, scrollbar, NULL);
	XtAddCallback(scrollbar,
		XtNscrollProc, bandChangeListener,
		(XtPointer) bandSliderLabel);
	XtAddCallback(scrollbar,
		XtNjumpProc, bandChangeListener,
		(XtPointer) bandSliderLabel);
}

static void
createSpeedSlider(Widget form)
{
	Widget scrollbar;
	char message[BUFSIZ];
	int delay;

	XtVaGetValues(puzzle,
		XtNdelay, &delay, NULL);
	(void) sprintf(message, "Animation Speed: %d",
		MAX_SPEED + MIN_SPEED - delay - 1);
	scrollbar = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	speedSliderLabel = XtVaCreateManagedWidget(message,
		labelWidgetClass, form,
		XtNfromHoriz, scrollbar, NULL);
	XtAddCallback(scrollbar,
		XtNscrollProc, speedChangeListener,
		(XtPointer) speedSliderLabel);
	XtAddCallback(scrollbar,
		XtNjumpProc, speedChangeListener,
		(XtPointer) speedSliderLabel);
}

static void
createPopupMenu(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);
	wedgeLabel = XtVaCreateManagedWidget(defaultString,
		menuButtonWidgetClass, form,
		/*XtNwidth, 150,*/
		NULL);
	free(defaultString);
	wedgeMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, wedgeLabel, NULL);
	for (i = 0; i < num; i++) {
		w = XtVaCreateManagedWidget(list[i],
			smeBSBObjectClass, wedgeMenu, NULL);
		XtAddCallback(w, XtNcallback,
			(XtCallbackProc) wedgeListener,
			(XtPointer) (size_t) i);
	}
}

static void
puzzlePracticeCancelListener(Widget w, XtPointer clientData, XtPointer callData)
{
	XtUnmanageChild(practiceDialog);
}

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

	practiceDialog = XtCreatePopupShell(title,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		dialogWidgetClass, practiceDialog,
		XtNlabel, text, NULL);
	practiceOKDialog = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(practiceOKDialog, XtNcallback,
		puzzlePracticeListener, dialog);
	practiceCancelDialog = XtVaCreateManagedWidget("Cancel",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(practiceCancelDialog, XtNcallback,
		puzzlePracticeCancelListener, dialog);
}

static void
puzzleRandomizeCancelListener(Widget w, XtPointer clientData, XtPointer callData)
{
	XtUnmanageChild(randomizeDialog);
}

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

	randomizeDialog = XtCreatePopupShell(title,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		dialogWidgetClass, randomizeDialog,
		XtNlabel, text, NULL);
	randomizeOKDialog = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(randomizeOKDialog, XtNcallback,
		puzzleRandomizeListener, dialog);
	randomizeCancelDialog = XtVaCreateManagedWidget("Cancel",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(randomizeCancelDialog, XtNcallback,
		puzzleRandomizeCancelListener, dialog);
}

static void
puzzleSolveOKListener(Widget w, XtPointer clientData, XtPointer callData)
{
	XtUnmanageChild(solveDialog);
}

static void
createSolveInfo(char *text, char *title)
{
	Widget dialog;

	solveDialog = XtCreatePopupShell(title,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		dialogWidgetClass, solveDialog,
		XtNlabel, text, NULL);
	solveOKDialog = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(solveOKDialog, XtNcallback,
		puzzleSolveOKListener, dialog);
}
#endif

int
main(int argc, char **argv)
{
	int pixmapSize;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	Widget mainPanel, controlPanel;
	Widget movesRowCol, bandChangerRowCol, speedChangerRowCol;
#endif
#ifdef HAVE_MOTIF
	Widget menuBar, pullDownMenu, widget;
	Widget menuBarPanel;
	Widget labelRowCol, wedgesRowCol, bandsRowCol;
	Widget messageRowCol, switchRowCol;
	XmString fileString, playString;
	XmString getString, writeString, quitString;
	XmString undoString, redoString, clearString;
#ifdef EXTRA
	XmString practiceString;
#endif
	XmString randomizeString, solveString;
#ifdef EXTRA
	XmString incrementString, decrementString, orientizeString;
	XmString speedString, slowString;
#endif
	XmString soundString, perspectiveString;
	unsigned int i;
#elif defined(HAVE_ATHENA)
	Widget movesLabel, recordLabel;
	Widget switchRowCol;
	int wedges;
#endif

	progDsp = argv[0];
	topLevel = XtInitialize(argv[0], "Mball",
		options, XtNumber(options), &argc, argv);
	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");
#ifdef EXTRA
	practiceString = XmStringCreateSimple((char *) "Practice");
#endif
	randomizeString = XmStringCreateSimple((char *) "Randomize");
	solveString = XmStringCreateSimple((char *) "Auto-solve");
#ifdef EXTRA
	incrementString = XmStringCreateSimple((char *) "Increment Bands");
	decrementString = XmStringCreateSimple((char *) "Decrement Bands");
	orientizeString = XmStringCreateSimple((char *) "Orientize");
	speedString = XmStringCreateSimple((char *) "Speed >");
	slowString = XmStringCreateSimple((char *) "Slow <");
#endif
	soundString = XmStringCreateSimple((char *) "Sound @");
	perspectiveString = XmStringCreateSimple((char *) "Perspective");
	(void) XmVaCreateSimplePulldownMenu(menuBar, (char *) "playMenu",
		1, playMenuListener,
		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, orientizeString, 'O', NULL, NULL,
		XmVaPUSHBUTTON, speedString, '>', NULL, NULL,
		XmVaPUSHBUTTON, slowString, '<', NULL, NULL,
#endif
		XmVaPUSHBUTTON, soundString, '@', NULL, NULL,
		XmVaPUSHBUTTON, perspectiveString, 't', NULL, NULL, NULL);
	XmStringFree(undoString);
	XmStringFree(redoString);
	XmStringFree(clearString);
#ifdef EXTRA
	XmStringFree(practiceString);
#endif
	XmStringFree(randomizeString);
	XmStringFree(solveString);
#ifdef EXTRA
	XmStringFree(incrementString);
	XmStringFree(decrementString);
	XmStringFree(orientizeString);
	XmStringFree(speedString);
	XmStringFree(slowString);
#endif
	XmStringFree(soundString);
	XmStringFree(perspectiveString);
	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);
	solveDialog = createHelp(menuBar, (char *) solveHelp,
		(char *) "Solve");
	practiceDialog = createQuery(topLevel,
		(char *) "Are you sure you want to toggle the practice mode?",
		(char *) "Practice Query",
		(XtCallbackProc) puzzlePracticeListener);
	randomizeDialog = createQuery(topLevel,
		(char *) "Are you sure you want to randomize?",
		(char *) "Randomize Query",
		(XtCallbackProc) puzzleRandomizeListener);
	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_COLUMN, NULL);
#ifdef MOUSEBITMAPS
	{
		/* Takes up valuable real estate. */
		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("recordText",
		xmLabelGadgetClass, movesRowCol,
		XtVaTypedArg, XmNlabelString, XmRString, "Record", 7, NULL);
	recordText = XtVaCreateManagedWidget("0",
		xmLabelWidgetClass, movesRowCol, NULL);
	labelRowCol = XtVaCreateManagedWidget("labelRowCol",
		xmRowColumnWidgetClass, controlPanel, NULL);
	(void) XtVaCreateManagedWidget("WedgeText",
		xmLabelGadgetClass, labelRowCol,
		XtVaTypedArg, XmNlabelString, XmRString, "Wedges:", 8, NULL);
	wedgesRowCol = XtVaCreateManagedWidget("wedgesRowCol",
		xmRowColumnWidgetClass, labelRowCol,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_COLUMN,
		XmNradioBehavior, True, NULL);
	for (i = 0; i < XtNumber(wedgeString); i++) {
		wedge[i] = XtVaCreateManagedWidget(wedgeString[i],
			xmToggleButtonGadgetClass, wedgesRowCol,
			XmNradioBehavior, True, NULL);
		XtAddCallback(wedge[i], XmNvalueChangedCallback,
			(XtCallbackProc) wedgeListener,
			(XtPointer) ((size_t) i));
	}
	bandsRowCol = XtVaCreateManagedWidget("bandsRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	bandChangerRowCol = XtVaCreateManagedWidget("bandChangerRowCol",
		xmRowColumnWidgetClass, bandsRowCol,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(bandChangerRowCol, (char *) "Size:", NULL, 0));
	bandChanger = XtVaCreateManagedWidget("bandChanger",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, bandChangerRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_BANDS,
		XmNmaximumValue, MAX_BANDS,
		XmNposition, DEFAULT_BANDS,
		XmNwrap, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, bandChangerRowCol,
		XmNminimum, MIN_BANDS,
		XmNmaximum, MAX_BANDS,
		XmNvalue, DEFAULT_BANDS,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Bands", 6,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(bandChanger, XmNvalueChangedCallback,
		(XtCallbackProc) bandChangeListener, (XtPointer) NULL);
	speedChangerRowCol = XtVaCreateManagedWidget("speedChangerRowCol",
		xmRowColumnWidgetClass, bandsRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(speedChangerRowCol, (char *) "Animation Speed:", NULL, 0));
	speedChanger = XtVaCreateManagedWidget("speedChanger",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, speedChangerRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_SPEED,
		XmNmaximumValue, MAX_SPEED,
		XmNposition, MAX_SPEED - DEFAULT_DELAY,
		XmNwrap, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, speedChangerRowCol,
		XmNminimum, MIN_SPEED,
		XmNmaximum, MAX_SPEED,
		XmNvalue, MAX_SPEED - DEFAULT_DELAY,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Animation Speed", 16,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(speedChanger, XmNvalueChangedCallback,
		(XtCallbackProc) speedChangeListener, (XtPointer) NULL);
	switchRowCol = XtVaCreateManagedWidget("switchRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	orientizeSwitch = XtVaCreateManagedWidget("Oriented",
		xmToggleButtonWidgetClass, switchRowCol,
		XmNset, DEFAULT_ORIENT, NULL);
	XtAddCallback(orientizeSwitch, XmNvalueChangedCallback,
		(XtCallbackProc) orientToggle, (XtPointer) NULL);
	practiceSwitch = XtVaCreateManagedWidget("Practice",
		xmToggleButtonWidgetClass, switchRowCol,
		XmNset, DEFAULT_PRACTICE, NULL);
	XtAddCallback(practiceSwitch, XmNvalueChangedCallback,
		(XtCallbackProc) practiceToggle, (XtPointer) NULL);
	messageRowCol = XtVaCreateManagedWidget("messageRowCol",
		xmRowColumnWidgetClass, controlPanel, NULL);
	message = XtVaCreateManagedWidget("Play Masterball! (use mouse and keypad)",
		xmLabelWidgetClass, messageRowCol, NULL);
	puzzle = XtVaCreateManagedWidget("mball",
		mballWidgetClass, mainPanel, NULL);
#elif defined(HAVE_ATHENA)
	createPracticeQuery(
		(char *) "Are you sure you want to toggle the practice mode?",
		(char *) "Practice Query");
	createRandomizeQuery(
		(char *) "Are you sure you want to randomize?",
		(char *) "Randomize Query");
	createSolveInfo(
		(char *) solveHelp,
		(char *) "Auto-solve");
	mainPanel = XtVaCreateManagedWidget("mainPanel",
		formWidgetClass, topLevel, NULL);
	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, 9,
		XtNfromHoriz, movesLabel, NULL);
	recordLabel = XtVaCreateManagedWidget("Record",
		labelWidgetClass, movesRowCol,
		XtNborderWidth, 0,
		XtNfromHoriz, movesText, NULL);
	recordText = XtVaCreateManagedWidget("0",
		labelWidgetClass, movesRowCol,
		XtNfromHoriz, recordLabel, NULL);
	bandChangerRowCol = XtVaCreateManagedWidget("bandChangerRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, movesRowCol, NULL);
	wedgeMenu = XtVaCreateManagedWidget("webMenu",
		boxWidgetClass, bandChangerRowCol,
		XtNborderWidth, 0, NULL);
	bandChanger = XtVaCreateManagedWidget("bandChanger",
		boxWidgetClass, bandChangerRowCol,
		XtNborderWidth, 0,
		XtNfromHoriz, wedgeMenu, NULL);
	switchRowCol = XtVaCreateManagedWidget("switchRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, bandChangerRowCol, NULL);
	orientizeSwitch = XtVaCreateManagedWidget("Oriented",
		toggleWidgetClass, switchRowCol,
		XtNstate, DEFAULT_ORIENT, NULL);
	XtAddCallback(orientizeSwitch, XtNcallback,
		(XtCallbackProc) orientToggle, (XtPointer) NULL);
	practiceSwitch = XtVaCreateManagedWidget("Practice",
		toggleWidgetClass, switchRowCol,
		XtNstate, DEFAULT_PRACTICE,
		XtNfromHoriz, orientizeSwitch, NULL);
	XtAddCallback(practiceSwitch, XtNcallback,
		(XtCallbackProc) practiceToggle, (XtPointer) NULL);
	speedChangerRowCol = XtVaCreateManagedWidget("speedChangerRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, switchRowCol, NULL);
	speedChanger = XtVaCreateManagedWidget("speedChanger",
		boxWidgetClass, speedChangerRowCol,
		XtNborderWidth, 0, NULL);
	message = XtVaCreateManagedWidget("Welcome",
		labelWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, speedChangerRowCol, NULL);
	puzzle = XtVaCreateManagedWidget("mball",
		mballWidgetClass, mainPanel,
		XtNfromVert, controlPanel, NULL);
#else
	puzzle = XtVaCreateManagedWidget("mball",
		mballWidgetClass, 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
	descriptionDialog = createHelp(menuBar, (char *) descriptionHelp,
		(char *) "Description");
	featuresDialog = createScrollHelp(menuBar, (char *) featuresHelp,
		(char *) "Features");
	synopsisDialog = createHelp(menuBar, (char *) synopsisHelp,
		(char *) "Synopsis");
	referencesDialog = createHelp(menuBar, (char *) referencesHelp,
		(char *) "References");
	aboutDialog = createHelp(menuBar, (char *) aboutHelp,
		(char *) "About");
#elif defined(HAVE_ATHENA)
	XtVaGetValues(puzzle,
		XtNwedges, &wedges, NULL);
	createPopupMenu(wedgeMenu, wedgeString, (wedges - MIN_WEDGES) / 2,
		sizeof(wedgeString) / sizeof(*wedgeString));
	createBandSlider(bandChanger);
	createSpeedSlider(speedChanger);
#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));
	XtMainLoop();

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