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

/* Methods file for Mball */

#include "file.h"
#include "rngs.h"
#include "sound.h"
#include "MballP.h"
#include "Mball2dP.h"
#ifdef HAVE_OPENGL
#include "MballGLP.h"
#endif

#ifdef WINVER
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "C:/ProgramData/wpuzzles/wmball.ini"
#endif

#define SECTION "setup"

static const char *wedgeColorString[MAX_WEDGES] =
{
	"255 255 0",
	"0 255 0",
	"46 139 87",
	"0 0 255",
	"1 255 255",
	"255 0 255",
	"255 0 0",
	"255 165 0",
	"255 192 203",
	"210 180 140",
	"176 196 222",
	"205 92 92"
};

static char wedgeColorChar[MAX_WEDGES] =
{'Y', 'G', 'S', 'B', 'C', 'M', 'R', 'O', 'P', 'T', 'L', 'I'};
#else

#if defined( USE_SOUND ) && defined( USE_NAS )
Display *dsp;
#endif

#ifndef LOGPATH
#ifdef VMS
#define LOGPATH "SYS$SCRATCH:"
#else
#define LOGPATH "/usr/tmp"
#endif
#endif

/*static void resizePuzzle(MballWidget w);
static void sizePuzzle(MballWidget w);*/
static Boolean setValuesPuzzle(Widget current, Widget request, Widget renew);
static void destroyPuzzle(Widget old);
static void initializePuzzle(Widget request, Widget renew);
/*static void exposePuzzle(Widget renew, XEvent *event, Region region);*/

MballClassRec mballClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Mball",	/* class name */
		sizeof (MballRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) initializePuzzle,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		NULL,		/* actions */
		0,		/* num actions */
		NULL,		/* resources */
		0,		/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		(XtWidgetProc) destroyPuzzle,	/* destroy */
		NULL,		/* resize */
		NULL,		/* expose */
		(XtSetValuesFunc) setValuesPuzzle,	/* set values */
		NULL,		/* set values hook */
		XtInheritSetValuesAlmost,	/* set values almost */
		NULL,		/* get values hook */
		NULL,		/* accept focus */
		XtVersion,	/* version */
		NULL,		/* callback private */
		NULL,		/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	}
};

WidgetClass mballWidgetClass = (WidgetClass) & mballClassRec;

void
setPuzzle(MballWidget w, int reason)
{
	mballCallbackStruct cb;

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

static void
setPuzzleMove(MballWidget w, int reason, int wedge, int band,
		int direction, int control, int fast)
{
	mballCallbackStruct cb;

	cb.reason = reason;
	cb.wedge = wedge;
	cb.band = band;
	cb.direction = direction;
	cb.control = control;
	cb.fast = fast;
	XtCallCallbacks((Widget) w, (char *) XtNselectCallback, &cb);
}
#endif

static void
loadFont(MballWidget w)
{
#ifndef WINVER
	Display *display = XtDisplay(w);
	const char *altfontname = "-*-times-*-r-*-*-*-180-*";
	char buf[512];

	if (w->mball.fontInfo) {
		XUnloadFont(XtDisplay(w), w->mball.fontInfo->fid);
		XFreeFont(XtDisplay(w), w->mball.fontInfo);
	}
	if (w->mball.font && (w->mball.fontInfo =
			XLoadQueryFont(display, w->mball.font)) == NULL) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 512,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->mball.font, altfontname);
#else
		(void) sprintf(buf,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->mball.font, altfontname);
#endif
		DISPLAY_WARNING(buf);
		if ((w->mball.fontInfo = XLoadQueryFont(display,
				altfontname)) == NULL) {
#ifdef HAVE_SNPRINTF
			(void) snprintf(buf, 512,
				"Cannot open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
#else
			(void) sprintf(buf,
				"Cannot open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
#endif
			DISPLAY_WARNING(buf);
		}
	}
	if (w->mball.fontInfo) {
		w->mball.letterOffset.x = XTextWidth(w->mball.fontInfo, "8", 1)
			/ 2;
		w->mball.letterOffset.y = w->mball.fontInfo->max_bounds.ascent
			/ 2;
	} else
#endif
	{
		w->mball.letterOffset.x = 3;
		w->mball.letterOffset.y = 4;
	}
}

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

static int mapDirToWedge[(MAX_WEDGES - MIN_WEDGES) / 2 + 1][COORD] =
{
	{
		0, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6
	},
	{
		0, 6, 6, 1, 6, 6, 0, 6, 6, 1, 6, 6
	},
	{
		0, 6, 1, 6, 2, 6, 0, 6, 1, 6, 2, 6
	},
	{
		0, 6, 1, 2, 3, 6, 0, 6, 1, 2, 3, 6
	},
	{
		0, 1, 2, 6, 3, 4, 0, 1, 2, 6, 3, 4
	},
	{
		0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5
	}
};

int mapWedgeToDir[(MAX_WEDGES - MIN_WEDGES) / 2 + 1][MAX_WEDGES] =
{
	{
		0, 6, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12
	},
	{
		0, 3, 6, 9, 12, 12, 12, 12, 12, 12, 12, 12
	},
	{
		0, 2, 4, 6, 8, 10, 12, 12, 12, 12, 12, 12
	},
	{
		0, 2, 3, 4, 6, 8, 9, 10, 12, 12, 12, 12
	},
	{
		0, 1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 12
	},
	{
		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
	}
};

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

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

	if (w->mball.wedges < MIN_WEDGES || w->mball.wedges > MAX_WEDGES) {
		intCat(&buf1,
			"Number of wedges out of bounds, use even ",
			MIN_WEDGES);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAX_WEDGES);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_WEDGES);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->mball.wedges = DEFAULT_WEDGES;
	}
	if (w->mball.bands < MIN_BANDS) {
		intCat(&buf1,
			"Number of bands out of bounds, use at least ",
			MIN_BANDS);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_BANDS);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->mball.bands = DEFAULT_BANDS;
	}
#if 0
	if (w->mball.delay < 0) {
		intCat(&buf1, "Delay cannot be negative (",
			w->mball.delay);
		stringCat(&buf2, buf1, "), taking absolute value");
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		w->mball.delay = -w->mball.delay;
	}
#endif
	if (w->mball.base < MIN_BASE || w->mball.base > MAX_BASE) {
		intCat(&buf1, "Base out of bounds, use ", MIN_BASE);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAX_BASE);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_BASE);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->mball.base = DEFAULT_BASE;
	}
}

#ifdef DEBUG
void
printPuzzle(MballWidget w)
{
	int wedge, band;

	for (wedge = 0; wedge < w->mball.wedges; wedge++) {
		for (band = 0; band < w->mball.bands; band++) {
			(void) printf("%d %d  ", w->mball.mballLoc[wedge][band].wedge,
				w->mball.mballLoc[wedge][band].direction);
		}
		(void) printf("\n");
	}
	(void) printf("\n");
}

#endif

Boolean
checkSolved(MballWidget w)
{
	int wedge, band;
	MballLoc test = {0, FALSE};

	if (w->mball.orient) {
		for (wedge = 0; wedge < w->mball.wedges; wedge++) {
			if (wedge == 0) {
				test.wedge = w->mball.mballLoc[wedge][0].wedge;
				test.direction = w->mball.mballLoc[wedge][0].direction;
			}
			for (band = 0; band < w->mball.bands; band++) {
				if (test.direction != w->mball.mballLoc[wedge][band].direction) {
					return False;
				}
				if (test.direction == 0) {
					if ((w->mball.wedges + w->mball.mballLoc[wedge][band].wedge -
							test.wedge) % w->mball.wedges != wedge) {
						return False;
					}
				} else {
					if ((w->mball.wedges - w->mball.mballLoc[wedge][band].wedge +
							test.wedge) % w->mball.wedges != wedge) {

						return False;
					}
				}
			}
		}
	} else {
		for (wedge = 0; wedge < w->mball.wedges; wedge++)
			for (band = 0; band < w->mball.bands; band++)
				if (band == 0) {
					test.wedge = w->mball.mballLoc[wedge][band].wedge;
					/* test.direction = w->mball.mballLoc[wedge][band].direction; */
				} else if (test.wedge != w->mball.mballLoc[wedge][band].wedge)
					return False;
	}
	return True;
}

static void
drawSector(MballWidget w, int sector, int offset)
{
	if (w->mball.dim == 2)
		drawSector2D((Mball2DWidget) w, sector, offset);
}

#if 0
static void
drawSectorAll(MballWidget w, int sector, int offset)
{
	drawSector(w, sector, offset);
#ifdef HAVE_OPENGL
	if (w->mball.dim == 4)
		drawAllPiecesGL((MballGLWidget) w);
#endif
}
#endif

static void
drawWedge(MballWidget w, int wedge)
{
	if (w->mball.dim == 2)
		drawWedge2D((Mball2DWidget) w, wedge);
}

void
drawAllWedges(MballWidget w)
{
	int wedge;


#ifdef HAVE_OPENGL
	if (w->mball.dim == 4)
		drawAllPiecesGL((MballGLWidget) w);
#endif
	for (wedge = 0; wedge < w->mball.wedges; wedge++)
		drawWedge(w, wedge);
}

static void
drawFrame(MballWidget w, Boolean focus)
{
	if (w->mball.dim == 2)
		drawFrame2D((Mball2DWidget) w, focus);
/*#ifdef HAVE_OPENGL
	else if (w->mball.dim == 4)
		drawFrameGL((MballGLWidget) w, focus);
#endif*/
}

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

static void
swapPieces(MballWidget w, int wedge1, int wedge2)
{
	MballLoc temp;
	int band;

	if (wedge1 == wedge2) {
		for (band = 0; band < w->mball.bands / 2; band++) {
			temp = w->mball.mballLoc[wedge1][band];
			w->mball.mballLoc[wedge1][band] =
				w->mball.mballLoc[wedge1][w->mball.bands - 1 - band];
			w->mball.mballLoc[wedge1][w->mball.bands - 1 - band] = temp;
		}
		for (band = 0; band < w->mball.bands; band++)
			w->mball.mballLoc[wedge1][band].direction =
				!w->mball.mballLoc[wedge1][band].direction;
		drawWedge(w, wedge1);
	} else {
		for (band = 0; band < w->mball.bands; band++) {
			temp = w->mball.mballLoc[wedge1][band];
			w->mball.mballLoc[wedge1][band] =
				w->mball.mballLoc[wedge2][w->mball.bands - 1 - band];
			w->mball.mballLoc[wedge2][w->mball.bands - 1 - band] = temp;
			w->mball.mballLoc[wedge1][band].direction =
				!w->mball.mballLoc[wedge1][band].direction;
			w->mball.mballLoc[wedge2][w->mball.bands - 1 - band].direction =
				!w->mball.mballLoc[wedge2][w->mball.bands - 1 - band].direction;
		}
		drawWedge(w, wedge1);
		drawWedge(w, wedge2);
	}
}

static void
movePieces(MballWidget w, int wedge, int band, int direction,
		Boolean control, int fast)
{
	int i;

#ifdef HAVE_OPENGL
	if (!control && fast != INSTANT && w->mball.dim == 4) {
		movePiecesGL((MballGLWidget) w, wedge, band, direction,
			control, fast);
	}
#endif
	if (direction == CW || direction == CCW) {	/* rotate band */
		int newI;
		MballLoc temp1 = {0, 0}, temp2;

		for (i = 0; i < w->mball.wedges; i++) {
			newI = (direction == CW) ? i : w->mball.wedges - 1 - i;
			if (newI == ((direction == CW) ? 0 : w->mball.wedges - 1)) {
				temp1 = w->mball.mballLoc[newI][band];
				w->mball.mballLoc[newI][band] = w->mball.mballLoc
					[((direction == CW) ? w->mball.wedges - 1 : 0)][band];
			} else {
				temp2 = temp1;
				temp1 = w->mball.mballLoc[newI][band];
				w->mball.mballLoc[newI][band] = temp2;
			}
			drawSector(w, newI + w->mball.wedges * band, FALSE);
		}
	} else {		/* flip */
		int sphereDir = mapDirToWedge[(w->mball.wedges - MIN_WEDGES) / 2][direction];
		int offset = w->mball.wedges / 2;
		int wedge1, wedge2;

		for (i = 0; i < w->mball.wedges / 2; i++)
			if (wedge == i + sphereDir)
				offset = 0;
		for (i = 0; i < (w->mball.wedges + 2) / 4; i++) {
			wedge1 = (i + sphereDir + offset) % w->mball.wedges;
			wedge2 = (w->mball.wedges / 2 - 1 - i + sphereDir + offset) %
				w->mball.wedges;
			swapPieces(w, wedge1, wedge2);
		}
	}
#ifdef HAVE_OPENGL
	if (!control && fast == INSTANT && w->mball.dim == 4) {
		drawAllPiecesGL((MballGLWidget) w);
	}
#endif
}

static void
moveControlCb(MballWidget w, int wedge, int direction, int fast)
{
	int band;

#ifdef HAVE_OPENGL
	if (fast != INSTANT && w->mball.dim == 4) {
		movePiecesGL((MballGLWidget) w, wedge, 0, direction,
			True, fast);
	}
#endif
	if (direction > COORD)
		for (band = 0; band < w->mball.bands; band++) {
			movePieces(w, wedge, band, direction, True, fast);
			/*setPuzzle(w, ACTION_CONTROL);*/
		}
	else {
		movePieces(w, 0, 0, direction, True, fast);
		/*setPuzzle(w, ACTION_CONTROL);*/
		movePieces(w, w->mball.wedges / 2, 0, direction, True, fast);
		/*setPuzzle(w, ACTION_CONTROL);*/
	}
#ifdef HAVE_OPENGL
	if (fast == INSTANT && w->mball.dim == 4) {
		drawAllPiecesGL((MballGLWidget) w);
	}
#endif
}

void
movePuzzle(MballWidget w, int wedge, int band, int direction, int control, int fast)
{
	if (control) {
		moveControlCb(w, wedge, direction, fast);
	} else {
		movePieces(w, wedge, band, direction, False, fast);
	}
	setPuzzleMove(w, ACTION_MOVED, wedge, band, direction,
		control, fast);
#ifdef USE_SOUND
	if (w->mball.sound) {
		playSound(MOVESOUND);
	}
#endif
	setMove(&undo, direction, control, wedge + w->mball.wedges * band);
	flushMoves(w, &redo, FALSE);
}

void
movePuzzleDelay(MballWidget w, int wedge, int band, int direction, int control)
{
	movePuzzle(w, wedge, band, direction, control, NORMAL);
	Sleep((unsigned int) w->mball.delay);
}

static Boolean
positionToSector(MballWidget w, int x, int y,
		int *sector, int *view)
{
	if (w->mball.dim == 2)
		return positionToSector2D((Mball2DWidget) w,
			x, y, sector, view);
#ifdef HAVE_OPENGL
	else if (w->mball.dim == 4)
		return positionToSectorGL((MballGLWidget) w,
			x, y, sector, view);
#endif
	*view = 0;
	return False;
}

static Boolean
positionSector(MballWidget w, int x, int y,
		int *sector, int *direction)
{
	int view, inside;

	inside = positionToSector(w, x, y, sector, &view);
	if ((*direction == CW || *direction == CCW) && !inside)
		return False;
	if (view == DOWN) {
		if (*direction == CCW)
			*direction = CW;
		else if (*direction == CW)
			*direction = CCW;
		else if (*direction < COORD)
			*direction = (COORD - *direction) % COORD;
	}
	if (*direction != CW && *direction != CCW &&
		mapDirToWedge[(w->mball.wedges - MIN_WEDGES) / 2][*direction] ==
			CUTS) {
		return False;
	}
	return True;
}

void
movePuzzleInput(MballWidget w, int x, int y, int direction, int control)
{
	int sector, wedge, band;

	if (checkSolved(w) && !w->mball.practice && !control) {
		moveNoPieces(w);
		return;
	}
	if (!positionSector(w, x, y, &sector, &direction))
		return;
	control = (control) ? 1 : 0;
	wedge = sector % w->mball.wedges;
	band = sector / w->mball.wedges;
	movePuzzle(w, wedge, band, direction, control, NORMAL);
	if (!control && checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

void
resetPieces(MballWidget w)
{
	int wedge, band;

	for (wedge = 0; wedge < MAX_WEDGES; wedge++) {
		if (w->mball.mballLoc[wedge])
			free(w->mball.mballLoc[wedge]);
		if (!(w->mball.mballLoc[wedge] = (MballLoc *)
				malloc(sizeof (MballLoc) *
					(size_t) w->mball.bands))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		if (startLoc[wedge])
			free(startLoc[wedge]);
		if (!(startLoc[wedge] = (MballLoc *)
				malloc(sizeof (MballLoc) *
					(size_t) w->mball.bands))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	for (wedge = 0; wedge < w->mball.wedges; wedge++)
		for (band = 0; band < w->mball.bands; band++) {
			w->mball.mballLoc[wedge][band].wedge = wedge;
			w->mball.mballLoc[wedge][band].direction = DOWN;
		}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->mball.currentDirection = IGNORE_DIR;
	w->mball.started = False;
}

static void
readFile(MballWidget w, FILE *fp, char *name)
{
	int c, i, wedge, band, orient, practice, moves;
	char *buf1 = NULL, *buf2 = NULL;

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &wedge) != 1) {
		(void) printf("corrupt record, expecting an integer for wedge\n");
		return;
	}
	if (wedge >= MIN_WEDGES && wedge <= MAX_WEDGES && !(wedge % 2)) {
		if (w->mball.wedges != wedge) {
			setPuzzle(w, (wedge - MIN_WEDGES) / 2 + ACTION_WEDGE2);
		}
	} else {
		stringCat(&buf1, name, " corrupted: wedge ");
		intCat(&buf2, buf1, wedge);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_WEDGES);
		free(buf1);
		stringCat(&buf1, buf2, " and ");
		free(buf2);
		intCat(&buf2, buf1, MAX_WEDGES);
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &band) != 1) {
		(void) printf("corrupt record, expecting an integer for band\n");
		return;
	}
	if (band >= MIN_BANDS) {
		for (i = w->mball.bands; i < band; i++) {
			setPuzzle(w, ACTION_INCREMENT);
		}
		for (i = w->mball.bands; i > band; i--) {
			setPuzzle(w, ACTION_DECREMENT);
		}
	} else {
		stringCat(&buf1, name, " corrupted: band ");
		intCat(&buf2, buf1, band);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_BANDS);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &orient) != 1) {
		(void) printf("corrupt record, expecting an integer for orient\n");
		return;
	}
	if (w->mball.orient != (Boolean) orient) {
		setPuzzle(w, ACTION_ORIENTIZE);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &practice) != 1) {
		(void) printf("corrupt record, expecting an integer for practice\n");
		return;
	}
	if (w->mball.practice != (Boolean) practice) {
		setPuzzle(w, ACTION_PRACTICE);
	}
#ifdef WINVER
	resetPieces(w);
#endif
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &moves) != 1) {
		(void) printf("corrupt record, expecting an integer for moves\n");
		return;
	}
	if (!scanStartPosition(fp, w))
		return;
	setPuzzle(w, ACTION_RESTORE);
	setStartPosition(w);
	if (!scanMoves(fp, w, moves))
		return;
	(void) printf("%s: wedge %d, band %d, orient %s, ",
		name, wedge, band, BOOL_STRING(orient));
	(void) printf("practice %s, moves %d\n", BOOL_STRING(practice), moves);
}

static void
getPieces(MballWidget w)
{
	FILE *fp;
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname, *name;

	stringCat(&buf1, CURRENTDELIM, LOGFILE);
	lname = buf1;
	stringCat(&buf1, LOGPATH, FINALDELIM);
	stringCat(&buf2, buf1, LOGFILE);
	free(buf1);
	fname = buf2;
	/* Try current directory first. */
	name = lname;
	if ((fp = fopen(name, "r")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "r")) == NULL) {
			stringCat(&buf1, "Cannot read ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot read ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	readFile(w, fp, name);
	(void) fclose(fp);
	free(lname);
	free(fname);
	w->mball.cheat = True; /* Assume the worst. */
}

static void
writePieces(MballWidget w)
{
	FILE *fp;
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname, *name;

	stringCat(&buf1, CURRENTDELIM, LOGFILE);
	lname = buf1;
	stringCat(&buf1, LOGPATH, FINALDELIM);
	stringCat(&buf2, buf1, LOGFILE);
	free(buf1);
	fname = buf2;
	/* Try current directory first. */
	name = lname;
	if ((fp = fopen(name, "w")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "w")) == NULL) {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	(void) fprintf(fp, "wedges%c %d\n", SYMBOL, w->mball.wedges);
	(void) fprintf(fp, "bands%c %d\n", SYMBOL, w->mball.bands);
	(void) fprintf(fp, "orient%c %d\n", SYMBOL, (w->mball.orient) ? 1 : 0);
	(void) fprintf(fp, "practice%c %d\n", SYMBOL,
		(w->mball.practice) ? 1 : 0);
	(void) fprintf(fp, "moves%c %d\n", SYMBOL,
		numMoves(&undo));
	printStartPosition(fp, w);
	printMoves(fp, &undo);
	(void) fclose(fp);
	(void) printf("Saved to %s\n", name);
	free(lname);
	free(fname);
}

static void
undoPieces(MballWidget w)
{
	if (madeMoves(&undo) &&
			w->mball.currentSector <= IGNORE_DIR) {
		int direction, sector, wedge, band, control;

		getMove(&undo, &direction, &control, &sector);
		setMove(&redo, direction, control, sector);
		direction = (direction < COORD) ? direction : 3 * COORD - direction;
		wedge = sector % w->mball.wedges;
		band = sector / w->mball.wedges;
		if (control) {
			moveControlCb(w, wedge, direction, DOUBLE);
			setPuzzleMove(w, ACTION_MOVED, wedge, band, direction,
				True, DOUBLE);
		} else {
			movePieces(w, wedge, band, direction, False, DOUBLE);
			setPuzzleMove(w, ACTION_UNDO, wedge, band, direction,
				False, DOUBLE);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
}

static void
redoPieces(MballWidget w)
{
	if (madeMoves(&redo) &&
			w->mball.currentSector <= IGNORE_DIR) {
		int direction, sector, wedge, band, control;

		getMove(&redo, &direction, &control, &sector);
		setMove(&undo, direction, control, sector);
		wedge = sector % w->mball.wedges;
		band = sector / w->mball.wedges;
		if (control) {
			moveControlCb(w, wedge, direction, DOUBLE);
			setPuzzleMove(w, ACTION_MOVED, wedge, band, direction,
				True, DOUBLE);
		} else {
			movePieces(w, wedge, band, direction, False, DOUBLE);
			setPuzzleMove(w, ACTION_REDO, wedge, band, direction,
				False, DOUBLE);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
}

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

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

static void
randomizePieces(MballWidget w)
{
	int randomDirection, wedge, band;
	int big = w->mball.wedges * (w->mball.bands + 1) + NRAND(2);

	if (w->mball.currentSector > IGNORE_DIR)
		return;
	w->mball.cheat = False;
	if (w->mball.practice)
		practicePieces(w);
	setPuzzle(w, ACTION_RESET);
	if (big > 100)
		big = 100;
#ifdef DEBUG
	big = 3;
#endif
	while (big--) {
		wedge = NRAND(w->mball.wedges);
		band = NRAND(w->mball.bands);
		do
			randomDirection = NRAND(2 * COORD);
		while (randomDirection < COORD &&
			mapDirToWedge[(w->mball.wedges - MIN_WEDGES) / 2][randomDirection] ==
				CUTS);
		if (randomDirection >= COORD) {
			if (randomDirection - COORD < CUTS)
				randomDirection = CW;
			else
				randomDirection = CCW;
		}
		movePuzzle(w, wedge, band, randomDirection,				FALSE, INSTANT);
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	setPuzzle(w, ACTION_RANDOMIZE);
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
solvePieces(MballWidget w)
{
#ifdef TRY_SOLVE
	solveSomePieces(w);
#else
	if (checkSolved(w) || w->mball.currentSector > IGNORE_DIR)
		return;
	if (w->mball.wedges <= 8 || w->mball.bands == 1) {
		solveSomePieces(w);
	} else {
		setPuzzle(w, ACTION_SOLVE_MESSAGE);
	}
#endif
}

static void
incrementPieces(MballWidget w)
{
	setPuzzle(w, ACTION_INCREMENT);
}

static Boolean
decrementPieces(MballWidget w)
{
	if (w->mball.bands <= MIN_BANDS)
		return False;
	setPuzzle(w, ACTION_DECREMENT);
	return True;
}

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

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

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

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

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

static void
perspectivePieces(MballWidget w)
{
	setPuzzle(w, ACTION_PERSPECTIVE);
}

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

	w->mball.wedges = (int) GetPrivateProfileInt(SECTION,
		"wedges", DEFAULT_WEDGES, INIFILE);
	w->mball.bands = (int) GetPrivateProfileInt(SECTION,
		"bands", DEFAULT_BANDS, INIFILE);
	w->mball.orient = (BOOL) GetPrivateProfileInt(SECTION,
		"orient", DEFAULT_ORIENT, INIFILE);
	w->mball.practice = (BOOL) GetPrivateProfileInt(SECTION,
		"practice", DEFAULT_PRACTICE, INIFILE);
	w->mball.base = (int) GetPrivateProfileInt(SECTION,
		"base", DEFAULT_BASE, INIFILE);
	w->mball.perspective = (BOOL) GetPrivateProfileInt(SECTION,
		"perspective", DEFAULT_PERSPECTIVE, INIFILE);
/*#ifdef HAVE_OPENGL
	w->mball.dim = (int) GetPrivateProfileInt(SECTION,
		"dim", 4, INIFILE);
#else
#endif FIXME when GL select working */
	w->mball.dim = (int) GetPrivateProfileInt(SECTION,
		"dim", 2, INIFILE);
	w->mball.view = (int) GetPrivateProfileInt(SECTION,
		"view", 1, INIFILE);
	w->mball.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->mball.reverse = (BOOL) GetPrivateProfileInt(SECTION,
		"reverseVideo", DEFAULT_REVERSE, INIFILE);
	/* cyan */
	(void) GetPrivateProfileString(SECTION,
		"frameColor", "0 255 254", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->mball.frameGC = RGB(color.red, color.green, color.blue);
	/* gray25 */
	(void) GetPrivateProfileString(SECTION,
		"pieceBorder", "64 64 64", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->mball.borderGC = RGB(color.red, color.green, color.blue);
	/* #AEB2C3 */
	(void) GetPrivateProfileString(SECTION,
		"background", "174 178 195", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->mball.inverseGC = RGB(color.red, color.green, color.blue);
	for (wedge = 0; wedge < MAX_WEDGES; wedge++) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "wedgeColor%d", wedge);
#else
		(void) sprintf(buf, "wedgeColor%d", wedge);
#endif
		(void) GetPrivateProfileString(SECTION,
			buf, wedgeColorString[wedge],
			szBuf, sizeof (szBuf), INIFILE);
		(void) sscanf(szBuf, "%d %d %d",
			&(color.red), &(color.green), &(color.blue));
		w->mball.wedgeGC[wedge] =
			RGB(color.red, color.green, color.blue);
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "wedgeChar%d", wedge);
#else
		(void) sprintf(buf, "wedgeChar%d", wedge);
#endif
		charbuf[0] = wedgeColorChar[wedge];
		charbuf[1] = '\0';
		(void) GetPrivateProfileString(SECTION,
			buf, charbuf, szBuf, sizeof (szBuf), INIFILE);
		w->mball.wedgeChar[wedge] = szBuf[0];
	}
	w->mball.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", 10, INIFILE);
	w->mball.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", FALSE, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"moveSound", MOVESOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->mball.moveSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->mball.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->mball.scoreFile, szBuf, 80);
}

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

void
resizePuzzle(MballWidget w)
{
	if (w->mball.dim == 2)
		resizePuzzle2D((Mball2DWidget) w);
#ifdef HAVE_OPENGL
	else if (w->mball.dim == 4)
		resizePuzzleGL((MballGLWidget) w);
#endif
}

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

void
exposePuzzle(MballWidget w)
{
	if (w->mball.dim == 2)
		exposePuzzle2D((Mball2DWidget) w);
#ifdef HAVE_OPENGL
	else if (w->mball.dim == 4)
		exposePuzzleGL((MballGLWidget) w);
#endif
}

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

	valueMask = GCForeground | GCBackground;
	if (w->mball.reverse) {
		values.background = w->mball.foreground;
	} else {
		values.background = w->mball.background;
	}
	if (!w->mball.mono || w->mball.dim == 4) {
		if (XAllocNamedColor(XtDisplay(w),
				DefaultColormapOfScreen(XtScreen(w)),
				w->mball.wedgeName[wedge], &colorCell, &rgb)) {
			values.foreground = w->mball.wedgeColor[wedge] = colorCell.pixel;
			if (w->mball.wedgeGC[wedge])
				XtReleaseGC((Widget) w, w->mball.wedgeGC[wedge]);
			w->mball.wedgeGC[wedge] = XtGetGC((Widget) w, valueMask, &values);
			if (w->mball.fontInfo)
				XSetFont(XtDisplay(w), w->mball.wedgeGC[wedge],
					w->mball.fontInfo->fid);
			return;
		} else {
			char *buf1, *buf2;

			stringCat(&buf1, "Color name \"",
				w->mball.wedgeName[wedge]);
			stringCat(&buf2, buf1, "\" is not defined for wedge ");
			free(buf1);
			intCat(&buf1, buf2, wedge);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
		}
	}
	if (w->mball.reverse) {
		values.background = w->mball.foreground;
		values.foreground = w->mball.background;
	} else {
		values.background = w->mball.background;
		values.foreground = w->mball.foreground;
	}
	if (w->mball.wedgeGC[wedge])
		XtReleaseGC((Widget) w, w->mball.wedgeGC[wedge]);
	w->mball.wedgeGC[wedge] = XtGetGC((Widget) w, valueMask, &values);
	if (w->mball.fontInfo)
		XSetFont(XtDisplay(w), w->mball.wedgeGC[wedge],
			w->mball.fontInfo->fid);
}

void
setAllColors(MballWidget w)
{
	XGCValues values;
	XtGCMask valueMask;
	int wedge;

	valueMask = GCForeground | GCBackground;

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

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

	checkPieces(w);
	for (wedge = 0; wedge < MAX_WEDGES; wedge++) {
		if (strcmp(w->mball.wedgeName[wedge], c->mball.wedgeName[wedge])) {
			setColors = True;
			break;
		}
	}
	if (w->mball.font != c->mball.font ||
			w->mball.borderColor != c->mball.borderColor ||
			w->mball.reverse != c->mball.reverse ||
			w->mball.mono != c->mball.mono ||
			setColors) {
		loadFont(w);
		setAllColors(w);
		redraw = True;
	} else if (w->mball.background != c->mball.background ||
			w->mball.foreground != c->mball.foreground) {
		setAllColors(w);
		redraw = True;
	}
	if (w->mball.orient != c->mball.orient ||
			w->mball.base != c->mball.base ||
			w->mball.practice != c->mball.practice) {
		resetPieces(w);
		redraw = True;
	}
	if (w->mball.perspective != c->mball.perspective) {
		redraw = True;
	}
	if (w->mball.menu != ACTION_IGNORE) {
		int menu = w->mball.menu;

		w->mball.menu = ACTION_IGNORE;
		switch (menu) {
		case ACTION_GET:
			getPieces(w);
			break;
		case ACTION_WRITE:
			writePieces(w);
			break;
		case ACTION_UNDO:
			undoPieces(w);
			break;
		case ACTION_REDO:
			redoPieces(w);
			break;
		case ACTION_CLEAR:
			clearPieces(w);
			break;
		case ACTION_PRACTICE:
			practicePieces(w);
			break;
		case ACTION_RANDOMIZE:
			randomizePieces(w);
			break;
		case ACTION_SOLVE:
			solvePieces(w);
			break;
		case ACTION_INCREMENT:
			incrementPieces(w);
			break;
		case ACTION_DECREMENT:
			(void) decrementPieces(w);
			break;
		case ACTION_ORIENTIZE:
			orientizePieces(w);
			break;
		case ACTION_SPEED:
			speedPieces(w);
			break;
		case ACTION_SLOW:
			slowPieces(w);
			break;
		case ACTION_SOUND:
			soundPieces(w);
			break;
		case ACTION_PERSPECTIVE:
			perspectivePieces(w);
			break;
		case ACTION_VIEW:
			viewPieces(w);
			break;
		default:
			break;
		}
	}
	if (w->mball.currentDirection == RESTORE_DIR) {
		setStartPosition(w);
		w->mball.currentDirection = IGNORE_DIR;
	} else if (w->mball.currentDirection == CLEAR_DIR) {
		w->mball.currentDirection = IGNORE_DIR;
		resetPieces(w);
		redraw = True;
		w->mball.currentDirection = IGNORE_DIR;
	} else if (w->mball.currentDirection > IGNORE_DIR) {
		int wedge = w->mball.currentWedge;
		int band = w->mball.currentBand;
		int direction = w->mball.currentDirection;
		w->mball.currentWedge = IGNORE_DIR;
		w->mball.currentBand = IGNORE_DIR;
		w->mball.currentDirection = IGNORE_DIR;
		if (w->mball.currentControl) {
			moveControlCb(w, wedge, direction,
				w->mball.currentFast);
		} else {
			(void) movePieces(w, wedge, band, direction,
				w->mball.currentControl, w->mball.currentFast);
		}
	}
	return (redraw);
}

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

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

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

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

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

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

void
selectPuzzle(MballWidget w
#ifdef WINVER
, const int x, const int y, const int control
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	int view;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int control = (int) (event->xkey.state & ControlMask);
#endif

	if (positionToSector(w, x, y, &(w->mball.currentSector), &view)) {
		if (control || w->mball.practice || !checkSolved(w))
			drawSector(w, w->mball.currentSector, TRUE);
	} else {
		w->mball.currentSector = IGNORE_DIR;
#ifdef HAVE_OPENGL
		if (w->mball.dim == 4)
			drawAllPiecesGL((MballGLWidget) w);
#endif

	}
}

void
releasePuzzle(MballWidget w
#ifdef WINVER
, const int x, const int y, const int shift, const int control
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	int sector, view, i, diff, opp;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int shift = (int) (event->xkey.state & (ShiftMask | LockMask));
	int control = (int) (event->xkey.state & ControlMask);
#endif

	if (w->mball.currentSector <= IGNORE_DIR)
		return;
	drawSector(w, w->mball.currentSector, FALSE);
	if (!control && !w->mball.practice && checkSolved(w))
		moveNoPieces(w);
	else if (positionToSector(w, x, y, &sector, &view)) {
		int currentWedge, currentBand;
		int wedge, band;

		currentWedge = w->mball.currentSector % w->mball.wedges;
		currentBand = w->mball.currentSector / w->mball.wedges;
		wedge = sector % w->mball.wedges;
		band = sector / w->mball.wedges;
		opp = (currentWedge + w->mball.wedges / 2) %
			w->mball.wedges;
		if (band == currentBand) {
			if (wedge == currentWedge) {
				w->mball.currentSector = IGNORE_DIR;
				return;
			}
			if (shift || (opp != (wedge + 1) % w->mball.wedges &&
					wedge != (opp + 1) % w->mball.wedges)) {
				/*setPuzzle(w, ACTION_AMBIGUOUS);*/
				diff = (currentWedge - wedge +
					w->mball.wedges) % w->mball.wedges;
				if (diff > w->mball.wedges / 2)
					for (i = 0; i < w->mball.wedges - diff; i++)
						movePuzzle(w, wedge, band,
							CW, control, NORMAL);
				else
					for (i = 0; i < diff; i++)
						movePuzzle(w, wedge, band,
							CCW, control, NORMAL);
				if (!control && checkSolved(w)) {
					setPuzzle(w, ACTION_SOLVED);
				}
				w->mball.currentSector = IGNORE_DIR;
				return;
			}
		}
		if (wedge == currentWedge && w->mball.wedges > 2) {
			setPuzzle(w, ACTION_AMBIGUOUS);
		} else if (opp == (wedge + 1) % w->mball.wedges) {
			movePuzzle(w, wedge, band,
				(mapWedgeToDir[(w->mball.wedges - MIN_WEDGES) / 2]
				[currentWedge] + 6) % 12, control, NORMAL);
			if (!control && checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		} else if (wedge == (opp + 1) % w->mball.wedges) {
			movePuzzle(w, wedge, band,
				mapWedgeToDir[(w->mball.wedges - MIN_WEDGES) /
				2][wedge], control, NORMAL);
			if (!control && checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		} else {
			moveNoPieces(w);
		}
	}
	w->mball.currentSector = IGNORE_DIR;
#ifdef HAVE_OPENGL
	if (w->mball.dim == 4) {
		drawAllPiecesGL((MballGLWidget) w);
	}
#endif
}

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

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

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

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

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

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

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

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

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

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

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

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

#ifndef WINVER
void
wedge2ModePuzzle(MballWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_WEDGE2);
}

void
wedge4ModePuzzle(MballWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_WEDGE4);
}

void
wedge6ModePuzzle(MballWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_WEDGE6);
}

void
wedge8ModePuzzle(MballWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_WEDGE8);
}

void
wedge10ModePuzzle(MballWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_WEDGE10);
}

void
wedge12ModePuzzle(MballWidget w
, XEvent *event, char **args, int nArgs
)
{
	setPuzzle(w, ACTION_WEDGE12);
}
#endif

void
incrementPuzzle(MballWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	incrementPieces(w);
}

#ifdef WINVER
Boolean
#else
void
#endif
decrementPuzzle(MballWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
#ifdef WINVER
	return
#else
	(void)
#endif
	decrementPieces(w);
}

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

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

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

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

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

void
perspectivePuzzle(MballWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	perspectivePieces(w);
}

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

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

#ifdef WINVER
void
wedgePuzzle(MballWidget w, const int mode)
{
	setPuzzle(w, mode + ACTION_WEDGE2);
}

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

#else

void
movePuzzleCcw(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, CCW,
		(int) (event->xkey.state & ControlMask));
}

void
movePuzzleCw(MballWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, CW,
		(int) (event->xkey.state & ControlMask));
}
#endif
