/*
 * @(#)Mball.c
 *
 * Copyright 1994 - 2012  David A. Bagley, bagleyd@tux.org
 *
 * 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"

#ifdef WINVER
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "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 quitPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void initializePuzzle(Widget request, Widget renew);
static void exposePuzzle(Widget renew, XEvent *event, Region region);
static void hidePuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void practicePuzzleWithQuery(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzleWithDoubleClick(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzleWithQuery(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzleWithDoubleClick(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void readPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void writePuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void undoPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void redoPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void clearPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void randomizePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void solvePuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void practicePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void incrementPuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void decrementPuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void orientizePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void wedge2ModePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void wedge4ModePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void wedge6ModePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void wedge8ModePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void wedge10ModePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void wedge12ModePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void speedUpPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void slowDownPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void toggleSoundPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void perspectivePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void enterPuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void leavePuzzle(MballWidget w, XEvent *event, char **args, int nArgs);
static void movePuzzleTtl(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTl(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTop(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTtr(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTr(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleCw(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBl(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBbl(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBbr(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBr(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleCcw(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleInput(MballWidget w,
	int x, int y, int direction, int control);
static void selectPuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);
static void releasePuzzle(MballWidget w,
	XEvent *event, char **args, int nArgs);

static char translations[] =
"<KeyPress>q: Quit()\n\
 Ctrl<KeyPress>C: Quit()\n\
 <KeyPress>osfCancel: Hide()\n\
 <KeyPress>Escape: Hide()\n\
 <KeyPress>osfEscape: Hide()\n\
 Ctrl<KeyPress>[: Hide()\n\
 <KeyPress>0x1B: Hide()\n\
 <KeyPress>0x2E: Speed()\n\
 <KeyPress>0x3E: Speed()\n\
 <KeyPress>0x3C: Slow()\n\
 <KeyPress>0x2C: Slow()\n\
 Shift<KeyPress>2: Sound()\n\
 <KeyPress>F11: MoveCcw()\n\
 <KeyPress>KP_Divide: MoveCcw()\n\
 <KeyPress>R5: MoveCcw()\n\
 <KeyPress>Home: MoveTl()\n\
 <KeyPress>KP_7: MoveTl()\n\
 <KeyPress>R7: MoveTl()\n\
 <KeyPress>Num_Lock: MoveTtl()\n\
 <KeyPress>Up: MoveTop()\n\
 <KeyPress>osfUp: MoveTop()\n\
 <KeyPress>KP_Up: MoveTop()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\n\
 <KeyPress>KP_Multiply: MoveTtr()\n\
 <KeyPress>Prior: MoveTr()\n\
 <KeyPress>KP_9: MoveTr()\n\
 <KeyPress>R9: MoveTr()\n\
 <KeyPress>Left: MoveLeft()\n\
 <KeyPress>osfLeft: MoveLeft()\n\
 <KeyPress>KP_Left: MoveLeft()\n\
 <KeyPress>KP_4: MoveLeft()\n\
 <KeyPress>R10: MoveLeft()\n\
 <KeyPress>F12: MoveCw()\n\
 <KeyPress>Begin: MoveCw()\n\
 <KeyPress>KP_5: MoveCw()\n\
 <KeyPress>R11: MoveCw()\n\
 <KeyPress>Right: MoveRight()\n\
 <KeyPress>osfRight: MoveRight()\n\
 <KeyPress>KP_Right: MoveRight()\n\
 <KeyPress>KP_6: MoveRight()\n\
 <KeyPress>R12: MoveRight()\n\
 <KeyPress>End: MoveBl()\n\
 <KeyPress>KP_1: MoveBl()\n\
 <KeyPress>R13: MoveBl()\n\
 <KeyPress>KP_0: MoveBbl()\n\
 <KeyPress>KP_Insert: MoveBbl()\n\
 <KeyPress>Down: MoveBottom()\n\
 <KeyPress>osfDown: MoveBottom()\n\
 <KeyPress>KP_Down: MoveBottom()\n\
 <KeyPress>KP_2: MoveBottom()\n\
 <KeyPress>R14: MoveBottom()\n\
 <KeyPress>KP_Decimal: MoveBbr()\n\
 <KeyPress>KP_Delete: MoveBbr()\n\
 <KeyPress>Next: MoveBr()\n\
 <KeyPress>KP_3: MoveBr()\n\
 <KeyPress>R15: MoveBr()\n\
 <Btn1Down>: Select()\n\
 <Btn1Up>: Release()\n\
 <Btn2Down>: PracticeMaybe()\n\
 <Btn2Down>(2+): Practice2()\n\
 <Btn3Down>: RandomizeMaybe()\n\
 <Btn3Down>(2+): Randomize2()\n\
 <Btn4Down>: MoveTop()\n\
 <Btn5Down>: MoveBottom()\n\
 <KeyPress>r: Read()\n\
 <KeyPress>w: Write()\n\
 <KeyPress>u: Undo()\n\
 <KeyPress>e: Redo()\n\
 <KeyPress>c: Clear()\n\
 <KeyPress>z: Randomize()\n\
 <KeyPress>s: Solve()\n\
 <KeyPress>p: Practice()\n\
 <KeyPress>i: Increment()\n\
 <KeyPress>d: Decrement()\n\
 <KeyPress>o: Orientize()\n\
 <KeyPress>2: Wedge2()\n\
 <KeyPress>4: Wedge4()\n\
 <KeyPress>6: Wedge6()\n\
 <KeyPress>8: Wedge8()\n\
 <KeyPress>0: Wedge10()\n\
 <KeyPress>=: Wedge12()\n\
 <KeyPress>.: Wedge12()\n\
 <KeyPress>t: Perspective()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

/* '=' is the 2nd key to the right of '0' on keyboard
 * '.' follows '0' on keypad */

static XtActionsRec actionsList[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "MoveCcw", (XtActionProc) movePuzzleCcw},
	{(char *) "MoveTl", (XtActionProc) movePuzzleTl},
	{(char *) "MoveTtl", (XtActionProc) movePuzzleTtl},
	{(char *) "MoveTop", (XtActionProc) movePuzzleTop},
	{(char *) "MoveTtr", (XtActionProc) movePuzzleTtr},
	{(char *) "MoveTr", (XtActionProc) movePuzzleTr},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveCw", (XtActionProc) movePuzzleCw},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBl", (XtActionProc) movePuzzleBl},
	{(char *) "MoveBbl", (XtActionProc) movePuzzleBbl},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleBottom},
	{(char *) "MoveBbr", (XtActionProc) movePuzzleBbr},
	{(char *) "MoveBr", (XtActionProc) movePuzzleBr},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "PracticeMaybe", (XtActionProc) practicePuzzleWithQuery},
	{(char *) "Practice2", (XtActionProc) practicePuzzleWithDoubleClick},
	{(char *) "RandomizeMaybe", (XtActionProc) randomizePuzzleWithQuery},
	{(char *) "Randomize2", (XtActionProc) randomizePuzzleWithDoubleClick},
	{(char *) "Read", (XtActionProc) readPuzzle},
	{(char *) "Write", (XtActionProc) writePuzzle},
	{(char *) "Undo", (XtActionProc) undoPuzzle},
	{(char *) "Redo", (XtActionProc) redoPuzzle},
	{(char *) "Clear", (XtActionProc) clearPuzzle},
	{(char *) "Randomize", (XtActionProc) randomizePuzzle},
	{(char *) "Solve", (XtActionProc) solvePuzzle},
	{(char *) "Practice", (XtActionProc) practicePuzzle},
	{(char *) "Increment", (XtActionProc) incrementPuzzle},
	{(char *) "Decrement", (XtActionProc) decrementPuzzle},
	{(char *) "Orientize", (XtActionProc) orientizePuzzle},
	{(char *) "Wedge2", (XtActionProc) wedge2ModePuzzle},
	{(char *) "Wedge4", (XtActionProc) wedge4ModePuzzle},
	{(char *) "Wedge6", (XtActionProc) wedge6ModePuzzle},
	{(char *) "Wedge8", (XtActionProc) wedge8ModePuzzle},
	{(char *) "Wedge10", (XtActionProc) wedge10ModePuzzle},
	{(char *) "Wedge12", (XtActionProc) wedge12ModePuzzle},
	{(char *) "Speed", (XtActionProc) speedUpPuzzle},
	{(char *) "Slow", (XtActionProc) slowDownPuzzle},
	{(char *) "Sound", (XtActionProc) toggleSoundPuzzle},
	{(char *) "Perspective", (XtActionProc) perspectivePuzzle},
	{(char *) "Enter", (XtActionProc) enterPuzzle},
	{(char *) "Leave", (XtActionProc) leavePuzzle}
};

static XtResource resources[] =
{
	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
	 XtOffset(MballWidget, core.width),
	 XtRString, (caddr_t) "200"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(MballWidget, core.height),
	 XtRString, (caddr_t) "400"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.frameColor),
	 XtRString, (caddr_t) "cyan" /*XtDefaultForeground*/},
	{XtNwedgeColor0, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[0]),
	 XtRString, (caddr_t) "yellow"},
	{XtNwedgeColor1, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[1]),
	 XtRString, (caddr_t) "green"},
	{XtNwedgeColor2, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[2]),
	 XtRString, (caddr_t) "SeaGreen"},
	{XtNwedgeColor3, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[3]),
	 XtRString, (caddr_t) "blue"},
	{XtNwedgeColor4, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[4]),
	 XtRString, (caddr_t) "cyan"},
	{XtNwedgeColor5, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[5]),
	 XtRString, (caddr_t) "magenta"},
	{XtNwedgeColor6, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[6]),
	 XtRString, (caddr_t) "red"},
	{XtNwedgeColor7, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[7]),
	 XtRString, (caddr_t) "orange"},
	{XtNwedgeColor8, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[8]),
	 XtRString, (caddr_t) "pink"},
	{XtNwedgeColor9, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[9]),
	 XtRString, (caddr_t) "tan"},
	{XtNwedgeColor10, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[10]),
	 XtRString, (caddr_t) "LightSteelBlue"},
	{XtNwedgeColor11, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.wedgeName[11]),
	 XtRString, (caddr_t) "IndianRed"},
	{XtNpieceBorder, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(MballWidget, mball.borderColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultForeground*/},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNwedges, XtCWedges, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.wedges),
	 XtRString, (caddr_t) "8"}, /* DEFAULT_WEDGES */
	{XtNbands, XtCBands, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.bands),
	 XtRString, (caddr_t) "4"}, /* DEFAULT_BANDS */
	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.orient),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULT_ORIENT */
	{XtNpractice, XtCPractice, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.practice),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_PRACTICE */
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.base),
	 XtRString, (caddr_t) "16"}, /* DEFAULT_BASE */
	{XtNperspective, XtCPerspective, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.perspective),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULT_PERSPECTIVE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(MballWidget, mball.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MballWidget, mball.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(MballWidget, mball.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(MballWidget, mball.select),
	 XtRCallback, (caddr_t) NULL}
};

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 */
		actionsList,	/* actions */
		XtNumber(actionsList),	/* num actions */
		resources,	/* resources */
		XtNumber(resources),	/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		(XtWidgetProc) destroyPuzzle,	/* destroy */
		(XtWidgetProc) resizePuzzle,	/* resize */
		(XtExposeProc) exposePuzzle,	/* 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 */
		translations,	/* 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);
}
#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) {
		(void) sprintf(buf,
			"Can not open %s font.\nAttempting %s font as alternate\n",
			w->mball.font, altfontname);
		DISPLAY_WARNING(buf);
		if ((w->mball.fontInfo = XLoadQueryFont(display,
				altfontname)) == NULL) {
			(void) sprintf(buf,
				"Can not open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
			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 Pixmap dr = 0; /* dummy for future double buffeband */

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 can not 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 int
int2String(MballWidget w, char *buf, int number, int base, Boolean capital)
{
	int digit, mult = base, last, position;
	int a, i, j, s, r;

	if (capital) {
		a = 'A', i = 'I', j = 'J', s = 'S', r = 'R';
	} else {
		a = 'a', i = 'i', j = 'j', s = 's', r = 'r';
	}
	if (number < 0) {
		char *buff;

		intCat(&buff, "int2String: 0 > ", number);
		DISPLAY_WARNING(buff);
		free(buff);
		return 0;
	}
	last = 1;
	while (number >= mult) {
		last++;
		mult *= base;
	}
	for (position = 0; position < last; position++) {
		mult /= base;
		digit = number / mult;
		number -= digit * mult;
		buf[position] = digit + '0';
		if (buf[position] > '9') {	/* ASCII */
			buf[position] += (a - '9' - 1);
		} else if (buf[position] < '0') {	/* EBCDIC */
			buf[position] += (a - '9' - 1);
			if (buf[position] > i)
				buf[position] += (j - i - 1);
			if (buf[position] > r)
				buf[position] += (s - r - 1);
		}
	}
	buf[last] = '\0';
	return last;
}

static void
letterPosition(MballWidget w, int wedge, int band, int lengthx, int lengthy,
		int *dx, int *dy)
{
	double angle, radius;

	angle = (2 * wedge + 1) * M_PI / w->mball.wedges;
	if (w->mball.perspective) {
		if (((w->mball.bands & 1) == 1) && band == w->mball.bands / 2)
			radius = sin(M_PI * (0.25 + band) / w->mball.bands) / 2.0;
		else 
			radius = sin(M_PI * (0.5 + band) / w->mball.bands) / 2.0;
	} else {
		if (((w->mball.bands & 1) == 1) && band == w->mball.bands / 2)
			radius = (4.0 * band + 1.0) / (4.0 * w->mball.bands);
		else
			radius = (2.0 * band + 1.0) / (2.0 * w->mball.bands);
	}
	*dx = lengthx / 2 + (int) (lengthx * radius * cos(angle - M_PI / 2));
	*dy = lengthy / 2 + (int) (lengthy * radius * sin(angle - M_PI / 2));
}

#if defined(WINVER) && (WINVER <= 0x030a)	/* if WINDOWS 3.1 or less */
#define FLOODFILL 1
#endif

#ifndef FLOODFILL
static void
offsetSect(const MballWidget w, const int wedge, int *dx, int *dy)
{
	double angle = (2 * wedge + 1) * M_PI / w->mball.wedges
		- M_PI / 2.0;

	*dx = (int) (w->mball.dr * cos(angle));
	*dy = (int) (w->mball.dr * sin(angle));
}
#endif

#ifdef WINVER
#ifdef FLOODFILL
/* Using a dangerous flood fill.  Probably faster then filling a sector by
 * drawing many arcs of width 1 */
static void
SECTOR(MballWidget w, const GC pieceGC, const GC borderGC,
		const int letterx, const int lettery)
{
	w->mball.hBrush = CreateSolidBrush(pieceGC);
	w->mball.hOldBrush = (HBRUSH) SelectObject(w->core.hDC,
		w->mball.hBrush);
	(void) FloodFill(w->core.hDC, letterx, lettery, borderGC);
	(void) SelectObject(w->core.hDC, w->mball.hOldBrush);
	(void) DeleteObject(w->mball.hBrush);
}

#else
static void
SECTOR(MballWidget w, const GC pieceGC, const GC borderGC,
		const int xo, const int yo,
		const int width1, const int height1,
		const int width2, const int height2,
		const int angle1, const int angle2)
{
	int d, r1 = MIN(width1, height1) / 2;
	int r2 = MIN(width2, height2) / 2;
	int t = MAX(r2 - r1 - 8, 1);

	if (r1 > r2) {
		d = r1;
		r1 = r2;
		r2 = d;
	} if (r1 < 0)
		r1 = -3;
	if (r1 <= 3) {
		d = MAX(2 * (r2 - 1), 3);
		DRAWPIE(w, pieceGC, xo - d / 2, yo - d / 2, d, d,
			angle1, angle2);
	} else {
		int a1 = angle1, a2 = angle2;

		d = MAX(r1 + r2 + 2, 2);

		/* MS weirdness here flat caps on wrong side of arc if
		   angle is 0, 90, 180 or 270 degrees. */
		/* https://forums.codegear.com/message.jspa?messageID=118084 */
		if (angle1 % 90 == 0) {
			if (angle1 != 0) {
				a1 -= 1;
				a2 += 1;
			} else {
				a1 = 359;
				a2 += 1;
			}
		}
		if ((angle1 + angle2) % 90 == 0) {
			a2 += 1;
		}
		DRAWARC2(w, dr, pieceGC, t, xo - d / 2, yo - d / 2, d, d,
			a1, a2);
		d = MAX(2 * (r1 + 3), 1);
		DRAWARC(w, dr, borderGC, 1, xo - d / 2, yo - d / 2, d, d,
			angle1, angle2);
	}
	DRAWARC(w, dr, borderGC, 1, xo - d / 2, yo - d / 2, d, d,
		angle1, angle2);
#if 0
	{
		double ang, x, y;
	if (r1 <= 0)
		r1 = 0;
	ang = RADIANS((double) angle1);
	x = cos(ang);
	y = -sin(ang);
	DRAWLINE(w, dr, pieceGC,
		(int) (r1 * x) + xo, (int) (r1 * y) + yo,
		(int) (r2 * x) + xo, (int) (r2 * y) + yo);
	ang = RADIANS((double) angle1+angle2);
	x = cos(ang);
	y = -sin(ang);
	DRAWLINE(w, dr, pieceGC,
		(int) (r1 * x) + xo, (int) (r1 * y) + yo,
		(int) (r2 * x) + xo, (int) (r2 * y) + yo);
	}
#endif
}
#endif

static void
drawSect(const MballWidget w, const GC pieceGC, const GC borderGC,
		const int r, const int wedge,
		const int startx, const int starty,
		const int lengthx, const int lengthy)
{
	int dx, dy, diam = 2 * w->mball.dr;
	double u, v;

#ifdef FLOODFILL
	letterPosition(w, wedge, r, lengthx, lengthy, &dx, &dy);
#ifdef DEBUG
	SetPixel(w->core.hDC, dx + startx, dy + starty, piece);
#else
	SECTOR(w, pieceGC, borderGC, dx + startx, dy + starty);
#endif
#else
	offsetSect(w, wedge, &dx, &dy);
	if ((w->mball.bands & 1) == 1) {
		if (r == w->mball.bands / 2) {
			if (w->mball.perspective) {
				u = sin(M_PI * r / w->mball.bands) / 2.0;
				v = 0.5;
			} else {
				u = (double) r / w->mball.bands;
				v = (double) (r + 1) / (w->mball.bands + 1);
			}
			SECTOR(w, pieceGC, borderGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
		} else {
			if (w->mball.perspective) {
				u = sin(M_PI * r / w->mball.bands) / 2.0;
				v = sin(M_PI * (r + 1) / w->mball.bands) / 2.0;
			} else {
				u = (double) r / w->mball.bands;
				v = (double) (r + 1) / w->mball.bands;
			}
			SECTOR(w, pieceGC, borderGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
		}
	} else {
		if (w->mball.perspective) {
			u = sin(M_PI * r / w->mball.bands) / 2.0;
			v = sin(M_PI * (r + 1) / w->mball.bands) / 2.0;
		} else {
			u = (double) r / w->mball.bands;
			v = (double) (r + 1) / w->mball.bands;
		}
		SECTOR(w, pieceGC, borderGC,
			startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
			(int) (u * 2 * lengthx - diam),
			(int) (u * 2 * lengthy - diam),
			(int) (v * 2 * lengthx - diam),
			(int) (v * 2 * lengthy - diam),
			CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
			-FULL_CIRCLE / w->mball.wedges);
	}
#endif
}

static void
drawSector(const MballWidget w,
		const int wedge, const int band, const int offset)
{
	int startx, starty, lengthx, lengthy, i, l;
	GC pieceGC, borderGC;

	startx = 1 + w->mball.puzzleOffset.x;
	starty = 1 + w->mball.puzzleOffset.y;
	lengthx = w->mball.viewLength - w->mball.delta +
		w->mball.puzzleOffset.x;
	lengthy = w->mball.viewLength - w->mball.delta +
		w->mball.puzzleOffset.y;
	if (offset == 0) {
		pieceGC = w->mball.wedgeGC[w->mball.mballLoc[wedge][band].wedge];
#ifdef FLOODFILL
		borderGC = (w->mball.focus) ? w->mball.frameGC :
			w->mball.borderGC;
#else
		borderGC = w->mball.borderGC;
#endif
	} else {
		pieceGC = w->mball.borderGC;
#ifdef FLOODFILL
		borderGC = (w->mball.focus) ? w->mball.frameGC :
			w->mball.borderGC;
#else
		borderGC = w->mball.wedgeGC[w->mball.mballLoc[wedge][band].wedge];
#endif
	}
	if (band < (w->mball.bands + 1) / 2) {
		drawSect(w, pieceGC, borderGC, band, wedge,
			startx, starty, lengthx - startx, lengthy - starty);
	}
	if (band + 1 > w->mball.bands / 2) {
		if (w->mball.vertical) {
			drawSect(w, pieceGC, borderGC,
				w->mball.bands - 1 - band,
				(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
				startx, lengthy + 3, lengthx - startx, lengthy - starty);
		} else {
			drawSect(w, pieceGC, borderGC,
				w->mball.bands - 1 - band, w->mball.wedges - 1 - wedge,
				lengthx + 3, starty, lengthx - startx, lengthy - starty);
		}
	}
	if (w->mball.mono) {
		int letterX, letterY;
		char buf[3];

		if (band < (w->mball.bands + 1) / 2) {
			letterPosition(w, wedge, band, lengthx - startx, lengthy - starty,
				&letterX, &letterY);
			letterX += startx + 6 + w->mball.letterOffset.x;
			letterY += starty + w->mball.letterOffset.y;
			if (w->mball.orient && !w->mball.mballLoc[wedge][band].direction) {
				int last;

				l = w->mball.mballLoc[wedge][band].wedge + 1;
				last = int2String(w, buf, l, w->mball.base, FALSE);
				buf[last] = w->mball.wedgeChar[l - 1];
				buf[last + 1] = '\0';
				i = 0;
				if (l == 0)
					l = 1;
				l *= w->mball.base;
				while (l >= 1) {
					l /= w->mball.base;
					letterX += w->mball.letterOffset.x;
					i++;
				}
			} else {
				l = w->mball.mballLoc[wedge][band].wedge + 1;
				buf[0] = w->mball.wedgeChar[l - 1];
				buf[1] = '\0';
				i = 1;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
		if (band + 1 > w->mball.bands / 2) {
			if (w->mball.vertical) {
				letterPosition(w,
					(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += startx + 5 + w->mball.letterOffset.x;
				letterY += lengthy + 3 + w->mball.letterOffset.y;
			} else {
				letterPosition(w,
					w->mball.wedges - 1 - wedge,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += lengthx + 8 + w->mball.letterOffset.x;
				letterY += starty + w->mball.letterOffset.y;
			}
			if (w->mball.orient && w->mball.mballLoc[wedge][band].direction) {
				int last;

				l = w->mball.mballLoc[wedge][band].wedge + 1;
				last = int2String(w, buf, l, w->mball.base, FALSE);
				buf[last] = w->mball.wedgeChar[l - 1];
				buf[last + 1] = '\0';
				i = 0;
				if (l == 0)
					l = 1;
				l *= w->mball.base;
				while (l >= 1) {
					l /= w->mball.base;
					letterX += w->mball.letterOffset.x;
					i++;
				}
			} else {
				l = w->mball.mballLoc[wedge][band].wedge + 1;
				buf[0] = w->mball.wedgeChar[l - 1];
				buf[1] = '\0';
				i = 1;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
	} else if (w->mball.orient) {
		int letterX, letterY;
		char buf[2];

		if (band < (w->mball.bands + 1) / 2 &&
				!w->mball.mballLoc[wedge][band].direction) {
			letterPosition(w, wedge, band, lengthx - startx, lengthy - starty,
				&letterX, &letterY);
			letterX += startx + 5 + w->mball.letterOffset.x;
			letterY += starty + w->mball.letterOffset.y;
			l = w->mball.mballLoc[wedge][band].wedge + 1;
			(void) int2String(w, buf, l, w->mball.base, TRUE);
			i = 0;
			if (l == 0)
				l = 1;
			while (l >= 1) {
				l /= w->mball.base;
				letterX += w->mball.letterOffset.x;
				i++;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
		if (band + 1 > w->mball.bands / 2 &&
				w->mball.mballLoc[wedge][band].direction) {
			if (w->mball.vertical) {
				letterPosition(w,
					(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += startx + 5 + w->mball.letterOffset.x;
				letterY += lengthy + 3 + w->mball.letterOffset.y;
			} else {
				letterPosition(w,
					w->mball.wedges - 1 - wedge,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += lengthx + 8 + w->mball.letterOffset.x;
				letterY += starty + w->mball.letterOffset.y;
			}
			l = w->mball.mballLoc[wedge][band].wedge + 1;
			(void) int2String(w, buf, l, w->mball.base, TRUE);
			i = 0;
			if (l == 0)
				l = 1;
			while (l >= 1) {
				l /= w->mball.base;
				letterX += w->mball.letterOffset.x;
				i++;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
	}
}

#else

static void
XFillSector(Display * display, Drawable drawable, GC gc, int xo, int yo,
		int width1, int height1, int width2, int height2,
		int angle1, int angle2)
{
	int d, r1 = MIN(width1, height1) / 2, r2 = MIN(width2, height2) / 2;
	int w = MAX(r2 - r1 - 8, 1);

	if (r1 > r2) {
		d = r1;
		r1 = r2;
		r2 = d;
	}
	if (r1 < 0)
		r1 = -3;
	d = MAX(r1 + r2 + 2, 2);
	XSetLineAttributes(display, gc, w, LineSolid, CapNotLast, JoinRound);
	XDrawArc(display, drawable, gc, xo - d / 2, yo - d / 2, d, d,
		angle1, angle2);
	XSetLineAttributes(display, gc, 1, LineSolid, CapNotLast, JoinRound);
}

static void
XDrawSector(Display * display, Drawable drawable, GC gc,
		const int xo, const int yo,
		const int width1, const int height1,
		const int width2, const int height2,
		const int angle1, const int angle2)
{
	int d, r1 = MIN(width1, height1) / 2, r2 = MIN(width2, height2) / 2;

	/*double ang, x, y; */

	if (r1 > r2) {
		d = r1;
		r1 = r2;
		r2 = d;
	}
	if (r1 < 0)
		r1 = -3;
	d = MAX(2 * (r1 + 3), 1);
	XDrawArc(display, drawable, gc, xo - d / 2, yo - d / 2, d, d,
		angle1, angle2);
	d = MAX(2 * (r2 - 1), 3);
	XDrawArc(display, drawable, gc, xo - d / 2, yo - d / 2, d, d,
		angle1, angle2);
#if 0
	ang = RADIANS((double) angle1 / MULT);
	x = cos(ang);
	y = sin(ang);
	XDrawLine(display, drawable, gc,
		(int) ((double) r1 * x) + xo, (int) ((double) r1 * y) + yo,
		(int) ((double) r2 * x) + xo, (int) ((double) r2 * y) + yo);
	ang = RADIANS((double) angle2 / MULT);
	x = cos(ang);
	y = sin(ang);
	XDrawLine(display, drawable, gc,
		(int) ((double) r1 * x) + xo, (int) ((double) r1 * y) + yo,
		(int) ((double) r2 * x) + xo, (int) ((double) r2 * y) + yo);
#endif
}

#if 0
static void
XFillSector(Display *display, Drawable drawable, GC gc,
		const int xo, const int yo,
		const int width1, const int height1,
		const int width2, const int height2,
		const int angle1, const int angle2)
{
	int d, r1 = MIN(width1, height1) / 2, r2 = MIN(width2, height2) / 2;

	if (r1 > r2) {
		d = r1;
		r1 = r2;
		r2 = d;
	}
	if (r1 < 0)
		r1 = -3;
	for (d = 2 * (r1 + 3); d < 2 * (r2 - 1); d++)
		XDrawArc(display, drawable, gc, xo - d / 2, yo - d / 2, d, d,
			angle1, angle2);
}
#endif

static void
drawSect(const MballWidget w, GC pieceGC, GC borderGC,
		const int r, const int wedge,
		const int startx, const int starty,
		const int lengthx, const int lengthy)
{
	int dx, dy, diam = 2 * w->mball.dr;
	double u, v;

	offsetSect(w, wedge, &dx, &dy);
	if ((w->mball.bands & 1) == 1) {
		if (r == w->mball.bands / 2) {
			if (w->mball.perspective) {
				u = sin(M_PI * r / w->mball.bands) / 2.0;
				v = 0.5;
			} else {
				u = (double) r / w->mball.bands;
				v = (double) (r + 1) / (w->mball.bands + 1);
			}
			XFillSector(XtDisplay(w), XtWindow(w), pieceGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
			XDrawSector(XtDisplay(w), XtWindow(w), borderGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
		} else {
			if (w->mball.perspective) {
				u = sin(M_PI * r / w->mball.bands) / 2.0;
				v = sin(M_PI * (r + 1) / w->mball.bands) / 2.0;
			} else {
				u = (double) r / w->mball.bands;
				v = (double) (r + 1) / w->mball.bands;
			}
			XFillSector(XtDisplay(w), XtWindow(w), pieceGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
			XDrawSector(XtDisplay(w), XtWindow(w), borderGC,
				startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
				(int) (u * 2 * lengthx - diam),
				(int) (u * 2 * lengthy - diam),
				(int) (v * 2 * lengthx - diam),
				(int) (v * 2 * lengthy - diam),
				CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
				-FULL_CIRCLE / w->mball.wedges);
		}
	} else {
		if (w->mball.perspective) {
			u = sin(M_PI * r / w->mball.bands) / 2.0;
			v = sin(M_PI * (r + 1) / w->mball.bands) / 2.0;
		} else {
			u = (double) r / w->mball.bands;
			v = (double) (r + 1) / w->mball.bands;
		}
		XFillSector(XtDisplay(w), XtWindow(w), pieceGC,
			startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
			(int) (u * 2 * lengthx - diam),
			(int) (u * 2 * lengthy - diam),
			(int) (v * 2 * lengthx - diam),
			(int) (v * 2 * lengthy - diam),
			CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
			-FULL_CIRCLE / w->mball.wedges);
		XDrawSector(XtDisplay(w), XtWindow(w), borderGC,
			startx + lengthx / 2 + dx, starty + lengthy / 2 + dy,
			(int) (u * 2 * lengthx - diam),
			(int) (u * 2 * lengthy - diam),
			(int) (v * 2 * lengthx - diam),
			(int) (v * 2 * lengthy - diam),
			CIRCLE_4 - FULL_CIRCLE * wedge / w->mball.wedges,
			-FULL_CIRCLE / w->mball.wedges);
	}
}

static void
drawSector(const MballWidget w, const int wedge, const int band,
		const int offset)
{
	GC pieceGC, borderGC;
	int startx, starty, lengthx, lengthy, i, l;

	startx = 1 + w->mball.puzzleOffset.x;
	starty = 1 + w->mball.puzzleOffset.y;
	lengthx = w->mball.viewLength - w->mball.delta + w->mball.puzzleOffset.x;
	lengthy = w->mball.viewLength - w->mball.delta + w->mball.puzzleOffset.y;
	if (offset) {
		borderGC = w->mball.wedgeGC[w->mball.mballLoc[wedge][band].wedge];
		if (w->mball.mono) {
			pieceGC = w->mball.inverseGC;
		} else {
			pieceGC = w->mball.borderGC;
		}
	} else {
		pieceGC = w->mball.wedgeGC[w->mball.mballLoc[wedge][band].wedge];
		borderGC = w->mball.borderGC;
	}
	/* if the number of bands is odd
	 * the band can straddle both hemispheres */
	if (band < (w->mball.bands + 1) / 2)
		drawSect(w, pieceGC, borderGC, band, wedge,
			startx, starty, lengthx - startx, lengthy - starty);
	if (band + 1 > w->mball.bands / 2) {
		if (w->mball.vertical)
			drawSect(w, pieceGC, borderGC,
				w->mball.bands - 1 - band,
				(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
				startx, lengthy + 3, lengthx - startx, lengthy - starty);
		else
			drawSect(w, pieceGC, borderGC,
				w->mball.bands - 1 - band,
				w->mball.wedges - 1 - wedge,
				lengthx + 3, starty, lengthx - startx, lengthy - starty);
	}
	if (w->mball.mono) {
		int letterX, letterY;
		char buf[66];

		if (offset) {
			borderGC = w->mball.borderGC;
		} else {
			borderGC = w->mball.inverseGC;
		}
		if (band < (w->mball.bands + 1) / 2) {
			letterPosition(w, wedge, band, lengthx - startx, lengthy - starty,
				&letterX, &letterY);
			letterX += startx + w->mball.letterOffset.x;
			letterY += starty + w->mball.letterOffset.y;
			if (w->mball.orient && !w->mball.mballLoc[wedge][band].direction) {
				int last;

				l = w->mball.mballLoc[wedge][band].wedge + 1;
				last = int2String(w, buf, l, w->mball.base, False);
				buf[last] = w->mball.wedgeName[l - 1][0];
				buf[last + 1] = '\0';
				i = 0;
				if (l == 0)
					l = 1;
				l *= w->mball.base;
				while (l >= 1) {
					l /= w->mball.base;
					letterX += w->mball.letterOffset.x;
					i++;
				}
			} else {
				buf[0] = w->mball.wedgeName[w->mball.mballLoc[wedge][band].wedge][0];
				buf[1] = '\0';
				i = 1;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
		if (band + 1 > w->mball.bands / 2) {
			if (w->mball.vertical) {
				letterPosition(w,
					(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += startx + w->mball.letterOffset.x;
				letterY += lengthy + 3 + w->mball.letterOffset.y;
			} else {
				letterPosition(w,
					w->mball.wedges - 1 - wedge,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += lengthx + 3 + w->mball.letterOffset.x;
				letterY += starty + w->mball.letterOffset.y;
			}
			if (w->mball.orient && w->mball.mballLoc[wedge][band].direction) {
				int last;

				l = w->mball.mballLoc[wedge][band].wedge + 1;
				last = int2String(w, buf, l, w->mball.base, False);
				buf[last] = w->mball.wedgeName[l - 1][0];
				buf[last + 1] = '\0';
				i = 0;
				if (l == 0)
					l = 1;
				l *= w->mball.base;
				while (l >= 1) {
					l /= w->mball.base;
					letterX += w->mball.letterOffset.x;
					i++;
				}
			} else {
				buf[0] = w->mball.wedgeName[w->mball.mballLoc[wedge][band].wedge][0];
				buf[1] = '\0';
				i = 1;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
	} else if (w->mball.orient) {
		int letterX, letterY;
		char buf[65];

		if (band < (w->mball.bands + 1) / 2 &&
				!w->mball.mballLoc[wedge][band].direction) {
			letterPosition(w, wedge, band, lengthx - startx, lengthy - starty,
				&letterX, &letterY);
			letterX += startx + w->mball.letterOffset.x;
			letterY += starty + w->mball.letterOffset.y;
			l = w->mball.mballLoc[wedge][band].wedge + 1;
			(void) int2String(w, buf, l, w->mball.base, True);
			i = 0;
			if (l == 0)
				l = 1;
			while (l >= 1) {
				l /= w->mball.base;
				letterX += w->mball.letterOffset.x;
				i++;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
		if (band + 1 > w->mball.bands / 2 &&
				w->mball.mballLoc[wedge][band].direction) {
			if (w->mball.vertical) {
				letterPosition(w,
					(3 * w->mball.wedges / 2 - 1 - wedge) % w->mball.wedges,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += startx + w->mball.letterOffset.x;
				letterY += lengthy + 3 + w->mball.letterOffset.y;
			} else {
				letterPosition(w,
					w->mball.wedges - 1 - wedge,
					w->mball.bands - 1 - band,
					lengthx - startx, lengthy - starty,
					&letterX, &letterY);
				letterX += lengthx + 3 + w->mball.letterOffset.x;
				letterY += starty + w->mball.letterOffset.y;
			}
			l = w->mball.mballLoc[wedge][band].wedge + 1;
			(void) int2String(w, buf, l, w->mball.base, True);
			i = 0;
			if (l == 0)
				l = 1;
			while (l >= 1) {
				l /= w->mball.base;
				letterX += w->mball.letterOffset.x;
				i++;
			}
			DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, i);
		}
	}
}
#endif

static void
drawWedge(MballWidget w, int wedge)
{
	int band;

	for (band = 0; band < w->mball.bands; band++)
		drawSector(w, wedge, band, FALSE);
}

void
drawAllWedges(MballWidget w)
{
	int wedge;

	for (wedge = 0; wedge < w->mball.wedges; wedge++)
		drawWedge(w, wedge);
}

static void
drawRadar(const MballWidget w, GC gc, int thick,
		const int startx, const int starty,
		const int lengthx, const int lengthy)
{
	int b, i, stx, sty, lenx, leny;
	double angle, increment, u;

	DRAWARC(w, dr, gc, thick, startx, starty,
		lengthx, lengthy, 0, FULL_CIRCLE);
	if ((w->mball.bands & 1) == 1) {
		stx = startx - lengthx / (2 * w->mball.bands) +
			(w->mball.bands / 2 + 1) * lengthx / w->mball.bands;
		sty = starty - lengthy / (2 * w->mball.bands) +
			(w->mball.bands / 2 + 1) * lengthy / w->mball.bands;
		for (b = 1; b < w->mball.bands / 2 + 1; b++) {
			if (w->mball.perspective) {
				u = sin(M_PI * b / w->mball.bands) / 2.0;
			} else {
				u = (double) b / w->mball.bands;
			}
			DRAWARC(w, dr, gc, thick,
				(int) (stx - lengthx * u),
				(int) (sty - lengthy * u),
				(int) (2 * lengthx * u),
				(int) (2 * lengthy * u),
				0, FULL_CIRCLE);
		}
	} else {
		stx = startx + (w->mball.bands / 2) * lengthx / w->mball.bands;
		sty = starty + (w->mball.bands / 2) * lengthy / w->mball.bands;
		for (b = 1; b < w->mball.bands / 2; b++) {
			if (w->mball.perspective) {
				u = sin(M_PI * b / w->mball.bands) / 2.0;
			} else {
				u = (double) b / w->mball.bands;
			}
			DRAWARC(w, dr, gc, thick,
				(int) (stx - lengthx * u),
				(int) (sty - lengthy * u),
				(int) (2 * lengthx * u),
				(int) (2 * lengthy * u),
				0, FULL_CIRCLE);
		}
	}
	increment = RADIANS(NUM_DEGREES) / w->mball.wedges;
	angle = RADIANS(RT_ANG);
	stx = startx + lengthx / 2;
	sty = starty + lengthy / 2;
	lenx = lengthx;
	leny = lengthy;
#ifdef WINVER
	lenx += 2;
	leny += 2;
#endif
	for (i = 0; i < w->mball.wedges; i++) {
		DRAWLINE(w, dr, gc,
			stx, sty,
			stx + (int) (lenx * cos(angle) / 2.0),
			sty + (int) (leny * sin(angle) / 2.0));
		angle += increment;
	}
}

static void
eraseFrame(const MballWidget w)
{
	FILLRECTANGLE(w, dr, w->mball.inverseGC,
		0, 0, w->core.width, w->core.height);
}

static void
drawFrame(MballWidget w, Boolean focus)
{
	int startx, starty, lengthx, lengthy;
	GC gc = (focus) ? w->mball.frameGC : w->mball.borderGC;

	startx = 1 + w->mball.puzzleOffset.x;
	starty = 1 + w->mball.puzzleOffset.y;
	lengthx = w->mball.viewLength - w->mball.delta + w->mball.puzzleOffset.x;
	lengthy = w->mball.viewLength - w->mball.delta + w->mball.puzzleOffset.y;
	drawRadar(w, gc, 0, startx, starty, lengthx - startx, lengthy - starty);
	if (w->mball.vertical) {
		DRAWLINE(w, dr, w->mball.frameGC, 0, lengthy + 1,
			w->core.width, lengthy + 1);
		drawRadar(w, gc, 0, startx, lengthy + 3,
			lengthx - startx, lengthy - starty);
	} else {
		DRAWLINE(w, dr, w->mball.frameGC, lengthx + 1, 0,
			lengthx + 1, w->core.height);
		drawRadar(w, gc, 0, lengthx + 3, starty,
			lengthx - startx, lengthy - starty);
	}
}

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)
{
	int i;

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

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

	if (direction > COORD)
		for (band = 0; band < w->mball.bands; band++) {
			movePieces(w, wedge, band, direction);
			setPuzzle(w, ACTION_CONTROL);
	} else {
		movePieces(w, 0, 0, direction);
		setPuzzle(w, ACTION_CONTROL);
		movePieces(w, w->mball.wedges / 2, 0, direction);
		setPuzzle(w, ACTION_CONTROL);
	}
}

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

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

static Boolean
positionToSector(MballWidget w, int positionX, int positionY,
		int *wedge, int *band, int *view)
{
	double angle, radius;
	int x = positionX - w->mball.puzzleOffset.x;
	int y = positionY - w->mball.puzzleOffset.y;

	if (w->mball.vertical && y > w->mball.viewLength - 1) {
		y -= (w->mball.viewLength - 1);
		*view = DOWN;
	} else if (!w->mball.vertical && x > w->mball.viewLength - 1) {
		x -= (w->mball.viewLength - 1);
		*view = DOWN;
	} else
		*view = UP;
	x -= (w->mball.wedgeLength + 1) / 2;
	y -= (w->mball.wedgeLength + 1) / 2;
	radius = sqrt((double) x * x + y * y);
	if (y >= 0)
		angle = atan2((double) -x, (double) y) + M_PI;
	else if (x < 0)
		angle = 2 * M_PI - atan2((double) -x, (double) -y);
	else
		angle = -atan2((double) -x, (double) -y);
	*band = 0;
	if (w->mball.perspective) {
		int i;

		for (i = 0; i < (w->mball.bands + 1) / 2; i++) {
			if (radius < w->mball.wedgeLength * sin(M_PI *
					(i + 1) / w->mball.bands) / 2.0) {
				*band = i;
				break;
			}
		}
	} else {
		*band = (int) (radius * w->mball.bands / w->mball.wedgeLength);
	}
	*wedge = (int) (angle * w->mball.wedges / (2.0 * M_PI));
	if (*view == DOWN) {
		if (w->mball.vertical)
			*wedge = (3 * w->mball.wedges / 2 - 1 - *wedge) % w->mball.wedges;
		else
			*wedge = (w->mball.wedges - 1 - *wedge) % w->mball.wedges;
		*band = w->mball.bands - 1 - *band;
	}
	if (radius > w->mball.wedgeLength / 2 + w->mball.delta)
		return False;
	return True;
}

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

	inside = positionToSector(w, x, y, wedge, band, &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;
}

#ifndef WINVER
static
#endif
void
movePuzzleInput(MballWidget w, int x, int y, int direction, int control)
{
	int wedge, band;

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

static 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) * w->mball.bands))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		if (startLoc[wedge])
			free(startLoc[wedge]);
		if (!(startLoc[wedge] = (MballLoc *)
				malloc(sizeof (MballLoc) * 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.currentWedge = IGNORE_DIR;
	w->mball.started = False;
}

static void
resizePieces(MballWidget w)
{
	w->mball.mballLength = w->mball.wedgeLength / (2 * w->mball.wedges) -
		w->mball.delta - 1;
	w->mball.letterOffset.x = -2;
	w->mball.letterOffset.y = 4;
	w->mball.dr = w->mball.wedges;
}

static void
readPieces(MballWidget w)
{
	FILE *fp;
	int c, i, wedge, band, orient, practice, moves;
	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, "Can not 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, "Can not read ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
		}
#endif
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &wedge);
	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);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &band);
	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);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &orient);
	if (w->mball.orient != (Boolean) orient) {
		setPuzzle(w, ACTION_ORIENTIZE);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &practice);
	if (w->mball.practice != (Boolean) practice) {
		setPuzzle(w, ACTION_PRACTICE);
	}
#ifdef WINVER
	resetPieces(w);
#endif
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &moves);
	scanStartPosition(fp, w);
	setPuzzle(w, ACTION_RESTORE);
	setStartPosition(w);
	scanMoves(fp, w, moves);
	(void) fclose(fp);
	(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);
	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, "Can not 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, "Can not write to ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
		}
#endif
	}
	(void) fprintf(fp, "wedge%c %d\n", SYMBOL, w->mball.wedges);
	(void) fprintf(fp, "band%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.currentWedge <= IGNORE_DIR) {
		int wedge, band, direction, control;

		getMove(&undo, &wedge, &band, &direction, &control);
		setMove(&redo, wedge, band, direction, control);
		direction = (direction < COORD) ? direction : 3 * COORD - direction;
		if (control)
			moveControlCb(w, wedge, direction);
		else {
			movePieces(w, wedge, band, direction);
			setPuzzle(w, ACTION_UNDO);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
}

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

		getMove(&redo, &wedge, &band, &direction, &control);
		setMove(&undo, wedge, band, direction, control);
		if (control)
			moveControlCb(w, wedge, direction);
		else {
			movePieces(w, wedge, band, direction);
			setPuzzle(w, ACTION_REDO);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
}

static void
clearPieces(MballWidget w)
{
	if (w->mball.currentWedge > IGNORE_DIR)
		return;
	resetPieces(w);
	drawAllWedges(w);
	setPuzzle(w, ACTION_RESET);
}

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.currentWedge > IGNORE_DIR)
		return;
	if (w->mball.practice)
		practicePieces(w);
	setPuzzle(w, ACTION_RESET);
	w->mball.cheat = False;
	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);
	}
	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 UNDERCONSTRUCTION
	solveSomePieces(w);
#else
	if (checkSolved(w) || w->mball.currentWedge > 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
speedPieces(MballWidget w)
{
	w->mball.delay -= 5;
	if (w->mball.delay < 0)
		w->mball.delay = 0;
#ifdef HAVE_MOTIF
	setPuzzle(w, ACTION_SPEED);
#endif
}

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

static void
soundPieces(MballWidget w)
{
	w->mball.sound = !w->mball.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 = GetPrivateProfileInt(SECTION,
		"wedges", DEFAULT_WEDGES, INIFILE);
	w->mball.bands = 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 = GetPrivateProfileInt(SECTION,
		"base", DEFAULT_BASE, INIFILE);
	w->mball.perspective = (BOOL) GetPrivateProfileInt(SECTION,
		"perspective", DEFAULT_PERSPECTIVE, 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++) {
		(void) sprintf(buf, "wedgeColor%d", wedge);
		(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);
		(void) sprintf(buf, "wedgeChar%d", wedge);
		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 = 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);
}

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

static 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.wedges != c->mball.wedges ||
			w->mball.bands != c->mball.bands) {
		sizePuzzle(w);
		redraw = True;
	}
	if (w->mball.mballLength != c->mball.mballLength) {
		resizePuzzle(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_READ:
			readPieces(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;
		default:
			break;
		}
	}
	return (redraw);
}

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

#if defined( USE_SOUND ) && defined( USE_ESOUND )
	(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);
}

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

#ifndef WINVER
static
#endif
void
resizePuzzle(MballWidget w)
{
	int tempLength;
#ifdef WINVER
	RECT rect;

	/* Determine size of client area */
	(void) GetClientRect(w->core.hWnd, &rect);
	w->core.width = rect.right;
	w->core.height = rect.bottom;
#endif

	w->mball.delta = 4;
	w->mball.vertical = (w->core.height >= w->core.width);
	if (w->mball.vertical)
		tempLength = MIN(w->core.height / 2, w->core.width);
	else
		tempLength = MIN(w->core.height, w->core.width / 2);
	w->mball.mballLength = MAX((tempLength - w->mball.delta + 1) /
				w->mball.wedges, 0);
	w->mball.wedgeLength = w->mball.wedges * w->mball.mballLength;
	w->mball.viewLength = w->mball.wedgeLength + w->mball.delta;
	w->mball.viewMiddle = w->mball.viewLength / 2;
	if (w->mball.vertical) {
		w->mball.puzzleSize.x = w->mball.viewLength - 1;
		w->mball.puzzleSize.y = 2 * w->mball.viewLength - w->mball.delta - 2;
	} else {
		w->mball.puzzleSize.x = 2 * w->mball.viewLength - w->mball.delta - 2;
		w->mball.puzzleSize.y = w->mball.viewLength - 1;
	}
	w->mball.puzzleOffset.x = ((int) w->core.width - w->mball.puzzleSize.x) / 2;
	w->mball.puzzleOffset.y = ((int) w->core.height - w->mball.puzzleSize.y) / 2;
	resizePieces(w);
}

#ifndef WINVER
static
#endif
void
sizePuzzle(MballWidget w)
{
	resetPieces(w);
	resizePuzzle(w);
}

#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;
	sizePuzzle(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
	(void) init_sound();
#endif
#endif
}

#ifndef WINVER
static
#endif
void
exposePuzzle(
#ifdef WINVER
MballWidget w
#else
Widget renew, XEvent *event, Region region
#endif
)
{
#ifndef WINVER
	MballWidget w = (MballWidget) renew;

	if (!w->core.visible)
		return;
#endif
	eraseFrame(w);
	drawFrame(w, w->mball.focus);
	drawAllWedges(w);
}

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

#ifndef WINVER
static
#endif
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.currentWedge),
				&(w->mball.currentBand), &view)) {
		if (control || w->mball.practice || !checkSolved(w))
			drawSector(w, w->mball.currentWedge, w->mball.currentBand,
				TRUE);
	} else
		w->mball.currentWedge = IGNORE_DIR;
}

#ifndef WINVER
static
#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 wedge, band, view, i, diff, opp;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int shift = (int) (event->xbutton.state & (ShiftMask | LockMask));
	int control = (int) (event->xkey.state & ControlMask);
#endif

	if (w->mball.currentWedge <= IGNORE_DIR)
		return;
	drawSector(w, w->mball.currentWedge, w->mball.currentBand, FALSE);
	if (!control && !w->mball.practice && checkSolved(w))
		moveNoPieces(w);
	else if (positionToSector(w, x, y, &wedge, &band, &view)) {
		opp = (w->mball.currentWedge + w->mball.wedges / 2) %
			w->mball.wedges;
		if (band == w->mball.currentBand) {
			if (wedge == w->mball.currentWedge) {
				w->mball.currentWedge = IGNORE_DIR;
				return;
			}
			if (shift || (opp != (wedge + 1) % w->mball.wedges && 
					wedge != (opp + 1) % w->mball.wedges)) {
				/*setPuzzle(w, ACTION_AMBIGUOUS);*/
				diff = (w->mball.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);
				else
					for (i = 0; i < diff; i++)
						movePuzzle(w, wedge, band, CCW,
							control);
				if (!control && checkSolved(w)) {
					setPuzzle(w, ACTION_SOLVED);
				}
				w->mball.currentWedge = IGNORE_DIR;
				return;
			}
		}
		if (wedge == w->mball.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]
				[w->mball.currentWedge], control);
			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);
			if (!control && checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		} else {
			moveNoPieces(w);
		}
	}
	w->mball.currentWedge = IGNORE_DIR;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#ifndef WINVER
static
#endif
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);
}

#else

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

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

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

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

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

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

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

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

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

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

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

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

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

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