/*-
# X-BASED BARREL(tm)
#
#  Barrel.c
#
###
#
#  Copyright (c) 2003 - 2005	David Albert 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 "playable",
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
*/

/* Methods file for Barrel */

#if defined(USE_RPLAY) || defined(USE_NAS) || defined(USE_VMSPLAY) || defined(USE_ESOUND) || defined(WINVER) || defined(DEF_PLAY)
#define USE_SOUND
extern void playSound(char * filename);
#endif
#include "BarrelP.h"

#ifndef PICTURE
#if 1
#define PICTURE ""
#else
#ifdef WINVER
#define PICTURE "picture"
#else
#ifdef HAVE_XPM
#define PICTURE "./mandrill.xpm"
#else
#define PICTURE "./mandrill.xbm"
#endif
#endif
#endif
#endif

#ifdef WINVER
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "wbarrel.ini"
#endif
#define SECTION "setup"

static const char *faceColorString[MAXCOLORS] =
{
	"0 255 0",
	"255 0 0",
	"255 255 0",
	"255 165 0",
	"0 0 255",
	"0 0 0"
};

static char faceColorChar[MAXCOLORS] =
{'G', 'R', 'Y', 'O', 'B', 'b'};
#else
#include "picture.h"

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

static Boolean SetValuesBarrel(Widget current, Widget request, Widget renew);
static void QuitBarrel(BarrelWidget w, XEvent * event, char **args, int nArgs);
static void DestroyBarrel(Widget old);
static void ResizeBarrel(BarrelWidget w);
static void InitializeBarrel(Widget request, Widget renew);
static void ExposeBarrel(Widget renew, XEvent * event, Region region);
static void HideBarrel(BarrelWidget w, XEvent * event, char **args, int nArgs);
static void SelectBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void MotionBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void ReleaseBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void PracticeBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void PracticeBarrelMaybe(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void PracticeBarrel2(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void RandomizeBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void RandomizeBarrelMaybe(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void RandomizeBarrel2(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void GetBarrel(BarrelWidget w, XEvent * event, char **args, int nArgs);
static void WriteBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void ClearBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void UndoBarrel(BarrelWidget w, XEvent * event, char **args, int nArgs);
static void SolveBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void OrientizeBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void PairsBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void SpeedBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void SlowBarrel(BarrelWidget w, XEvent * event, char **args, int nArgs);
static void SoundBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void EnterBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void LeaveBarrel(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void MoveBarrelTop(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void MoveBarrelBottom(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void MoveBarrelLeft(BarrelWidget w, XEvent * event,
	char **args, int nArgs);
static void MoveBarrelRight(BarrelWidget w, XEvent * event,
	char **args, int nArgs);

static char defaultTranslationsBarrel[] =
"<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>Up: MoveTop()\n\
 <KeyPress>osfUp: MoveTop()\n\
 <KeyPress>KP_Up: MoveTop()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\n\
 <KeyPress>Left: MoveLeft()\n\
 <KeyPress>osfLeft: MoveLeft()\n\
 <KeyPress>KP_Left: MoveLeft()\n\
 <KeyPress>KP_4: MoveLeft()\n\
 <KeyPress>R10: MoveLeft()\n\
 <KeyPress>Right: MoveRight()\n\
 <KeyPress>osfRight: MoveRight()\n\
 <KeyPress>KP_Right: MoveRight()\n\
 <KeyPress>KP_6: MoveRight()\n\
 <KeyPress>R12: MoveRight()\n\
 <KeyPress>Down: MoveBottom()\n\
 <KeyPress>osfDown: MoveBottom()\n\
 <KeyPress>KP_Down: MoveBottom()\n\
 <KeyPress>KP_2: MoveBottom()\n\
 <KeyPress>R14: MoveBottom()\n\
 <Btn1Down>: Select()\n\
 <Btn1Motion>: Motion()\n\
 <Btn1Up>: Release()\n\
 <KeyPress>p: Practice()\n\
 <KeyPress>r: Randomize()\n\
 <Btn3Down>: RandomizeMaybe()\n\
 <Btn3Down>(2+): Randomize2()\n\
 <Btn4Down>: MoveTop()\n\
 <Btn5Down>: MoveBottom()\n\
 <KeyPress>g: Get()\n\
 <KeyPress>w: Write()\n\
 <KeyPress>c: Clear()\n\
 <KeyPress>u: Undo()\n\
 <KeyPress>s: Solve()\n\
 <KeyPress>o: Orientize()\n\
 <KeyPress>i: Pairs()\n\
 <KeyPress>0x2E: Speed()\n\
 <KeyPress>0x3E: Speed()\n\
 <KeyPress>0x3C: Slow()\n\
 <KeyPress>0x2C: Slow()\n\
 <KeyPress>@: Sound()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsListBarrel[] =
{
	{(char *) "Quit", (XtActionProc) QuitBarrel},
	{(char *) "Hide", (XtActionProc) HideBarrel},
	{(char *) "MoveTop", (XtActionProc) MoveBarrelTop},
	{(char *) "MoveLeft", (XtActionProc) MoveBarrelLeft},
	{(char *) "MoveRight", (XtActionProc) MoveBarrelRight},
	{(char *) "MoveBottom", (XtActionProc) MoveBarrelBottom},
	{(char *) "Select", (XtActionProc) SelectBarrel},
	{(char *) "Motion", (XtActionProc) MotionBarrel},
	{(char *) "Release", (XtActionProc) ReleaseBarrel},
	{(char *) "Practice", (XtActionProc) PracticeBarrel},
	{(char *) "PracticeMaybe", (XtActionProc) PracticeBarrelMaybe},
	{(char *) "Practice2", (XtActionProc) PracticeBarrel2},
	{(char *) "Randomize", (XtActionProc) RandomizeBarrel},
	{(char *) "RandomizeMaybe", (XtActionProc) RandomizeBarrelMaybe},
	{(char *) "Randomize2", (XtActionProc) RandomizeBarrel2},
	{(char *) "Get", (XtActionProc) GetBarrel},
	{(char *) "Write", (XtActionProc) WriteBarrel},
	{(char *) "Clear", (XtActionProc) ClearBarrel},
	{(char *) "Undo", (XtActionProc) UndoBarrel},
	{(char *) "Solve", (XtActionProc) SolveBarrel},
	{(char *) "Orientize", (XtActionProc) OrientizeBarrel},
	{(char *) "Pairs", (XtActionProc) PairsBarrel},
	{(char *) "Speed", (XtActionProc) SpeedBarrel},
	{(char *) "Slow", (XtActionProc) SlowBarrel},
	{(char *) "Sound", (XtActionProc) SoundBarrel},
	{(char *) "Enter", (XtActionProc) EnterBarrel},
	{(char *) "Leave", (XtActionProc) LeaveBarrel}
};

static XtResource resourcesBarrel[] =
{
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.username),
	 XtRString, (caddr_t) "guest"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(BarrelWidget, barrel.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(BarrelWidget, barrel.background),
	 XtRString, (caddr_t) XtDefaultBackground},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(BarrelWidget, barrel.frameColor),
	 XtRString, (caddr_t) "cyan" /*XtDefaultForeground*/},
	{XtNtileColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(BarrelWidget, barrel.tileColor),
	 XtRString, (caddr_t) "gray75" /*XtDefaultForeground*/},
	{XtNtileBorder, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(BarrelWidget, barrel.borderColor),
	 XtRString, (caddr_t) "gray50" /*XtDefaultBackground*/},
	{XtNfaceColor0, XtCLabel, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.faceName[0]),
	 XtRString, (caddr_t) "Green"},
	{XtNfaceColor1, XtCLabel, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.faceName[1]),
	 XtRString, (caddr_t) "Red"},
	{XtNfaceColor2, XtCLabel, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.faceName[2]),
	 XtRString, (caddr_t) "Yellow"},
	{XtNfaceColor3, XtCLabel, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.faceName[3]),
	 XtRString, (caddr_t) "Orange"},
	{XtNfaceColor4, XtCLabel, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.faceName[4]),
	 XtRString, (caddr_t) "Blue"},
	{XtNfaceColor5, XtCLabel, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.faceName[5]),
	 XtRString, (caddr_t) "black"},
	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
	 XtOffset(BarrelWidget, core.width),
	 XtRString, (caddr_t) "512"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(BarrelWidget, core.height),
	 XtRString, (caddr_t) "512"},
	{XtNtiles, XtCTiles, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.tiles),
	 XtRString, (caddr_t) "6"}, /* DEFAULTTILES */
	{XtNfaces, XtCFaces, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.faces),
	 XtRString, (caddr_t) "5"}, /* DEFAULTFACES */
	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.orient),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULTORIENT */
	{XtNpairs, XtCPairs, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.pairs),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULTPAIRS */
	{XtNpractice, XtCPractice, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.practice),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULTPRACTICE */
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverse, XtCReverse, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNinstall, XtCInstall, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.install),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpicture, XtCPicture, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.picture),
	 XtRString, (caddr_t) PICTURE},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULTDELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNbumpSound, XtCBumpSound, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.bumpSound),
	 XtRString, (caddr_t) BUMPSOUND},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.base),
	 XtRString, (caddr_t) "10"}, /* DEFAULTBASE */
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.menu),
	 XtRString, (caddr_t) "-1"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(BarrelWidget, barrel.select),
	 XtRCallback, (caddr_t) NULL}
};

BarrelClassRec barrelClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Barrel",	/* class name */
		sizeof (BarrelRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) InitializeBarrel,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		actionsListBarrel,	/* actions */
		XtNumber(actionsListBarrel),	/* num actions */
		resourcesBarrel,	/* resources */
		XtNumber(resourcesBarrel),	/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		(XtWidgetProc) DestroyBarrel,	/* destroy */
		(XtWidgetProc) ResizeBarrel,	/* resize */
		(XtExposeProc) ExposeBarrel,	/* expose */
		(XtSetValuesFunc) SetValuesBarrel,	/* set values */
		NULL,		/* set values hook */
		XtInheritSetValuesAlmost,	/* set values almost */
		NULL,		/* get values hook */
		NULL,		/* accept focus */
		XtVersion,	/* version */
		NULL,		/* callback private */
		defaultTranslationsBarrel,	/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	}
};

WidgetClass barrelWidgetClass = (WidgetClass) & barrelClassRec;

#ifndef HAVE_USLEEP
#if !defined( VMS ) || defined( XVMSUTILS ) || ( __VMS_VER >= 70000000 )
#ifdef USE_XVMSUTILS
#include <X11/unix_time.h>
#endif
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#endif
#endif
#if defined(SYSV) || defined(SVR4)
#ifdef LESS_THAN_AIX3_2
#include <sys/poll.h>
#else /* !LESS_THAN_AIX3_2 */
#include <poll.h>
#endif /* !LESS_THAN_AIX3_2 */
#endif /* defined(SYSV) || defined(SVR4) */

/* not static in case usleep found in system include */
int
usleep(unsigned int usec)
{
#if (defined (SYSV) || defined(SVR4)) && !defined(__hpux)
#if defined(HAVE_NANOSLEEP)
	{
		struct timespec rqt;

		rqt.tv_nsec = 1000 * (usec % (unsigned int) 1000000);
		rqt.tv_sec = usec / (unsigned int) 1000000;
		return nanosleep(&rqt, NULL);
	}
#else
	(void) poll(
#if defined(__cplusplus) || defined(c_plusplus)
		(pollfd *) /* guess */
#else
		(void *)
#endif
		0, (int) 0, usec / 1000);	/* ms resolution */
#endif
#else
#ifdef VMS
	long timadr[2];

	if (usec != 0) {
		timadr[0] = -usec * 10;
		timadr[1] = -1;

		sys$setimr(4, &timadr, 0, 0, 0);
		sys$waitfr(4);
	}
#else
	struct timeval time_out;

#if 0
	/* (!defined(AIXV3) && !defined(__hpux)) */
	extern int select(int, fd_set *, fd_set *, fd_set *, struct timeval *);

#endif

	time_out.tv_usec = usec % (unsigned int) 1000000;
	time_out.tv_sec = usec / (unsigned int) 1000000;
	(void) select(0, (void *) 0, (void *) 0, (void *) 0, &time_out);
#endif
#endif
	return 0;
}
#endif

void
SetBarrel(BarrelWidget w, int reason)
{
	barrelCallbackStruct cb;

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

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

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

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

#if (!defined(WINVER) || (WINVER <= 0x030a)) /* if X or WINDOWS 3.1 or less */
static void
Sleep(unsigned int cMilliseconds)
{
#if (defined(WINVER) && (WINVER <= 0x030a))
	unsigned long time_out = GetTickCount() + cMilliseconds;

	while (time_out > GetTickCount());
#else
	(void) usleep(cMilliseconds * 1000);
#endif
}
#endif

static void
initTimer(BarrelWidget w)
{
#ifdef WINVER
	w->barrel.oldTime = GetTickCount();
#else
	XFlush(XtDisplay(w));
	(void) X_GETTIMEOFDAY(&(w->barrel.oldTime));
#endif
}


static void
useTimer(BarrelWidget w, int delay)
{
	int sleepTime;
#ifdef WINVER
	long tv = GetTickCount();

	sleepTime = delay - (tv - w->barrel.oldTime);
	w->barrel.oldTime = tv;
	if (sleepTime > 0) {
		Sleep((unsigned int) sleepTime);
	}
#else
	struct timeval tv;

	XFlush(XtDisplay(w));
	(void) X_GETTIMEOFDAY(&tv);
	sleepTime = delay * 1000 - ((tv.tv_sec - w->barrel.oldTime.tv_sec) *
		1000000 + tv.tv_usec - w->barrel.oldTime.tv_usec);
	w->barrel.oldTime.tv_sec = tv.tv_sec;
	w->barrel.oldTime.tv_usec = tv.tv_usec;
	if (sleepTime > 0) {
		(void) usleep((unsigned int) sleepTime);
	}
#endif
}

void
intCat(char ** string, const char * var1, const int var2)
{
	if (!(*string = (char *) malloc(strlen(var1) + 21))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	(void) sprintf(*string, "%s%d", var1, var2);
}

void
stringCat(char ** string, const char * var1, const char * var2)
{
	if (!(*string = (char *) malloc(strlen(var1) + strlen(var2) + 1))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	(void) sprintf(*string, "%s%s", var1, var2);
}

static void
CheckTiles(BarrelWidget w)
{
	char *buf1 = NULL, *buf2 = NULL;

	if (w->barrel.tiles < MINTILES) {
		intCat(&buf1,
			"Number of tiles in X direction out of bounds, use at least ",
			MINTILES);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULTTILES);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->barrel.tiles = DEFAULTTILES;
	}
	if (w->barrel.faces < MINFACES || w->barrel.faces > MAXFACES) {
		intCat(&buf1,
			"Number of faces out of bounds, use ",
			MINFACES);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAXFACES);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULTFACES);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->barrel.faces = DEFAULTFACES;
	}
	if (w->barrel.base < MINBASE || w->barrel.base > MAXBASE) {
		intCat(&buf1, "Base out of bounds, use ", MINBASE);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAXBASE);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULTBASE);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->barrel.base = DEFAULTBASE;
	}
}

static Boolean
PositionTile(BarrelWidget w, int x, int *r)
{
	*r = (x - w->barrel.delta.x / 2 - w->barrel.puzzleOffset.x) /
		w->barrel.offset.x;
	if (*r < 0 || *r >= w->barrel.tiles)
		return False;
	return True;
}

static int
Column(BarrelWidget w, int pos)
{
	return (pos % w->barrel.tiles);
}

static int
Row(BarrelWidget w, int pos)
{
	return (pos / w->barrel.tiles);
}


static int
CartesianX(BarrelWidget w, int pos)
{
	return Column(w, pos) * w->barrel.offset.x + w->barrel.delta.x +
		w->barrel.puzzleOffset.x;
}

static int
CartesianY(BarrelWidget w, int pos)
{
	return Row(w, pos) * (w->barrel.offset.y + 1) + w->barrel.delta.y +
		w->barrel.puzzleOffset.y;
}

static int
TileNFromSpace(BarrelWidget w, int plunger, int n, int direction)
{
	int pos = w->barrel.spacePosition[plunger];

	if (direction == LEFT || direction == RIGHT)
		return (pos + ((direction == RIGHT) ? -n : n));
	else /* direction == TOP || direction == BOTTOM */
		return (pos + (w->barrel.tiles * ((direction == BOTTOM) ?
			-n : n)));
}

static int
SolvedPosition(BarrelWidget w, int tile) {
	return (tile + 1) % w->barrel.tileFaces;
}

Boolean
CheckSolved(const BarrelWidget w)
{
	int color, i, j, inc;

	if (w->barrel.tiles < 2)
		return True;
	if (w->barrel.orient) {
		for (j = 0; j < w->barrel.faces; j++) {
			color = (w->barrel.tileOfPosition[j * w->barrel.tiles +
				1] - 1) / (w->barrel.tiles - 2);
			inc = (w->barrel.tileOfPosition[j * w->barrel.tiles +
				1] - 1) % (w->barrel.tiles - 2);
			for (i = 1; i < w->barrel.tiles - 2; i++) {
				if (color != (w->barrel.tileOfPosition[j *
						w->barrel.tiles + i + 1] - 1) /
					       	(w->barrel.tiles - 2)) {
					return False;
				}
				if (inc + i != (w->barrel.tileOfPosition[j *
						w->barrel.tiles + i + 1] - 1) %
					       	(w->barrel.tiles - 2)) {
					return False;
				}
			}
			if (color % 2 == 0) {
				if ((w->barrel.tiles - 2) * w->barrel.faces +
						1 + color / 2 !=
						w->barrel.tileOfPosition[j *
						w->barrel.tiles +
						w->barrel.tiles - 1])
					return False;
			}
		}
	} else {
		for (j = 0; j < w->barrel.faces; j++) {
			color = (w->barrel.tileOfPosition[j * w->barrel.tiles +
				1] - 1) / (w->barrel.tiles - 2);
			for (i = 0; i < w->barrel.tiles - 3; i++) {
				if (color != (w->barrel.tileOfPosition[j *
						w->barrel.tiles + i + 2] - 1) /
					       	(w->barrel.tiles - 2)) {
					return False;
				}
			}
		}
	}
	return True;
}

static int
TilePicture(BarrelWidget w, int tile)
{
	int face, inc = 0;

	if (!(w->barrel.picture && *(w->barrel.picture)))
		return tile;
	if (tile < 0)
		return -1;
	for (face = 0; face < w->barrel.faces; face++) {
		inc += w->barrel.tiles - 2;
		if (tile < inc)
			return tile + 2 * face + 1;
	}
	for (face = 0; face < NUMPLUNGERS; face++) {
		if (tile == inc++)
			return (face * 2 + 1) * w->barrel.tiles - 1;
	}
	for (face = 0; face < w->barrel.faces - NUMPLUNGERS; face++) {
		if (tile == inc++)
			return (2 * face + 1) * w->barrel.tiles;
		if (tile == inc++)
			return (2 * face + 2) * w->barrel.tiles - 1;
	}
	return 0;
}

#ifdef DEBUG
static void
PrintBarrel(BarrelWidget w)
{
	int face, tile, plunger;

	for (face = 0; face < w->barrel.faces; face++) {
		for (tile = 0; tile < w->barrel.tiles; tile++) {
			   (void) printf("%d %d, ",
	   w->barrel.tileOfPosition[face * w->barrel.tiles + tile],
   TilePicture(w, w->barrel.tileOfPosition[face * w->barrel.tiles + tile]));
			if (!((tile + 1) % w->barrel.tiles))
				(void) printf("\n");
		}
	}
	(void) printf("\n");
	for (plunger = 0; plunger < NUMPLUNGERS; plunger++) {
		(void) printf("%d: %d\n", plunger,
			w->barrel.spacePosition[plunger]);
	}
	(void) printf("\n");
}
#endif

static int
int2String(BarrelWidget 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
fill3DRect(BarrelWidget w, Pixmap dr, GC gc, GC darkerGC, GC brighterGC,
		int x, int y, int width, int height, Boolean raised)
{
	GC currentGC = (raised) ? gc : darkerGC;

	if (width > 2 && height > 2)
		FILLRECTANGLE(w, dr, currentGC,
			x + 1, y + 1, width - 2, height - 2);
	currentGC = (raised) ? brighterGC : darkerGC;
	FILLRECTANGLE(w, dr, currentGC,
		x, y, 1, height);
	if (width > 1)
		FILLRECTANGLE(w, dr, currentGC,
			x + 1, y, width - 2, 1);
	currentGC = (raised) ? darkerGC : gc;
	if (width > 1 && height > 1)
		FILLRECTANGLE(w, dr, currentGC,
			x, y + height - 1, width, 1);
	if (width > 1 && height > 1)
		FILLRECTANGLE(w, dr, currentGC,
			x + width - 1, y, 1, height - 1);
}

#define MULT 64
static void
DrawBall(BarrelWidget w, Pixmap dr, GC gc, int pos, int offsetX, int offsetY)
{
	int dx, dy, diameter;

	diameter = MIN(w->barrel.offset.x, w->barrel.offset.y) * 3 / 4 - 3;
	dx = CartesianX(w, pos) + w->barrel.tileSize.x / 2 - offsetX;
	dy = CartesianY(w, pos) + w->barrel.tileSize.y / 2 - offsetY;
	FILLCIRCLE(w, dr, gc, diameter, dx, dy);
}

static void
drawShadow(BarrelWidget w, Pixmap dr, GC gc, int startX, int startY,
		int sizeX, int sizeY)
{
	FILLRECTANGLE(w, dr, gc,
		startX, startY, sizeX + 1, 1);
	FILLRECTANGLE(w, dr, gc,
		startX, startY, 1, sizeY + 1);
}

static void
DrawTile(BarrelWidget w, int pos, Boolean blank, Boolean erase,
		int pressedOffset, int offsetX, int offsetY)
{
	int dx, dy, sx, sy;
	int tile = TilePicture(w, (w->barrel.tileOfPosition[pos] +
		w->barrel.tileFaces - 1) % w->barrel.tileFaces);
	Pixmap dr = 0;

	dx = CartesianX(w, pos) + offsetX + pressedOffset;
	dy = CartesianY(w, pos) + offsetY + pressedOffset;
	sx = CartesianX(w, tile);
	sy = CartesianY(w, tile);
	if (blank) {
		FILLRECTANGLE(w, dr,
			(erase) ? w->barrel.inverseGC : w->barrel.tileGC,
			dx, dy,
			w->barrel.tileSize.x, w->barrel.tileSize.y);
		return;
	}
#ifdef WINVER
	w->barrel.hOldBitmap = (HBITMAP) SelectObject(w->barrel.memDC,
		w->barrel.bufferTiles[pressedOffset]);
	BitBlt(w->core.hDC,
		dx, dy,
		w->barrel.tileSize.x, w->barrel.tileSize.y,
		w->barrel.memDC,
		sx, sy,
		SRCCOPY);
	SelectObject(w->barrel.memDC, w->barrel.hOldBitmap);
#else
	XSetGraphicsExposures(XtDisplay(w), w->barrel.tileGC, False);
	XCopyArea(XtDisplay(w),
		w->barrel.bufferTiles[pressedOffset],
		XtWindow(w),
		w->barrel.tileGC,
		sx, sy,
		w->barrel.tileSize.x, w->barrel.tileSize.y,
		dx, dy);
#endif
}

static void
DrawBufferedTile(BarrelWidget w, int pos, int pressedOffset)
{
	Pixmap *dr;
	GC ballGC, tileGC, borderGC;
	int dx, dy, tile = SolvedPosition(w, pos);
	int i = 0, digitOffsetX = -2, t = tile;
	char buf[66];

	dr = &(w->barrel.bufferTiles[pressedOffset]);
	tileGC = w->barrel.tileGC;
	borderGC = w->barrel.borderGC;
	if ((w->barrel.mono && pressedOffset) || tile <= 0) {
		ballGC = borderGC;
	} else {
		int face = ((tile - 1) / (w->barrel.tiles - 2)) % MAXCOLORS;

		ballGC = w->barrel.faceGC[face];
#ifndef WINVER
		if (!w->barrel.mono) {
			if (w->barrel.faceColor[face] == w->barrel.tileColor) {
				ballGC = (pressedOffset) ? tileGC : borderGC;
			}
		}
#endif
	}
	dr = &(w->barrel.bufferTiles[pressedOffset]);
	dx = CartesianX(w, pos) + pressedOffset;
	dy = CartesianY(w, pos) + pressedOffset;
	if (pressedOffset != 0) {
		drawShadow(w, *dr, w->barrel.tileDarkerGC,
			dx - pressedOffset, dy - pressedOffset,
			w->barrel.tileSize.x - 1, w->barrel.tileSize.y - 1);
	}
	DRAWRECTANGLE(w, *dr, borderGC, dx, dy,
		w->barrel.tileSize.x + 1, w->barrel.tileSize.y - 1);
	fill3DRect(w, *dr, tileGC,
		w->barrel.tileDarkerGC, w->barrel.tileBrighterGC,
		dx, dy, w->barrel.tileSize.x, w->barrel.tileSize.y,
		pressedOffset == 0);
	/* FILLRECTANGLE(w, *dr, tileGC,
		dx + 1, dy + 1,
		w->barrel.tileSize.x, w->barrel.tileSize.y - 2); */
	if (tile > 0 && tile < ((w->barrel.tiles - 2) * w->barrel.faces +
			NUMPLUNGERS + 1)) {
		DrawBall(w, *dr, ballGC, pos, pressedOffset, pressedOffset);
	}
	buf[0] = '\0';
	if (w->barrel.orient || w->barrel.mono) {
		if (w->barrel.mono) {
			if (w->barrel.orient) {
				int last;

				last = int2String(w, buf, tile, w->barrel.base, False);
				buf[last] =
#ifdef WINVER
					w->barrel.faceChar[((tile - 1) / (w->barrel.tiles - 2)) %
						MAXCOLORS];
#else
					w->barrel.faceName[((tile - 1) / (w->barrel.tiles -2)) %
						MAXCOLORS][0];
#endif
				buf[last + 1] = '\0';
				t *= w->barrel.base;
			} else {
				buf[0] =
#ifdef WINVER
					w->barrel.faceChar[((tile - 1) /
						(w->barrel.tiles - 2)) % MAXCOLORS];
#else
					w->barrel.faceName[((tile - 1) /
						(w->barrel.tiles - 2)) % MAXCOLORS][0];
#endif
				buf[1] = '\0';
				t = 1;
			}
		} else {
			(void) int2String(w, buf, tile, w->barrel.base, True);
		}
		while (t >= 1) {
			t /= w->barrel.base;
			digitOffsetX += w->barrel.digitOffset.x;
			i++;
		}
		DRAWTEXT(w, *dr, tileGC,
			dx + w->barrel.tileSize.x / 2 - digitOffsetX,
			dy + w->barrel.tileSize.y / 2 + w->barrel.digitOffset.y,
			buf, i);
	}
#if 0
	DRAWRECTANGLE(w, *dr, borderGC, dx, dy,
		w->barrel.tileSize.x + 1, w->barrel.tileSize.y - 1);
#endif
}

static void
DrawAllBufferedTiles(const BarrelWidget w)
{
	int k, l;

	for (k = 0; k < w->barrel.tileFaces; k++)
		for (l = 0; l < 2; l++)
			DrawBufferedTile(w, k, l);
}
void
DrawAllTiles(const BarrelWidget w)
{
	int k;

	for (k = 0; k < w->barrel.tileFaces; k++)
		DrawTile(w, k, (w->barrel.tileOfPosition[k] < 0),
			 (w->barrel.tileOfPosition[k] < 0), FALSE, 0, 0);
}

static void
AnimateSlide(BarrelWidget w, int numTiles, int dir, int fast)
{
	int inc, aTile, plunger;
	int fillBeginPos[NUMPLUNGERS];
	int fillBeginI = 0, gapI = 0, moveI = 0;
	int dx, dy, pos, posNext;
	int ix = 0, iy = 0;
	Pixmap dr = 0;

	for (plunger = 0; plunger < NUMPLUNGERS; plunger++) {
	  fillBeginPos[plunger] = TileNFromSpace(w, plunger, numTiles, dir);
	}
	fillBeginI = CartesianX(w, fillBeginPos[0]);
	dy = CartesianY(w, fillBeginPos[0]);
	gapI = w->barrel.tileSize.x * fast / w->barrel.numSlices;
	moveI = w->barrel.tileSize.x + w->barrel.delta.x;
	if (gapI == 0)
	  gapI++;
	if (numTiles < 0)
	  numTiles = -numTiles;
	initTimer(w);
	for (inc = 0; inc < moveI + gapI; inc += gapI) {
	  if (inc > moveI)
	    inc = moveI;
	  for (plunger = 0; plunger < NUMPLUNGERS; plunger++) {
	    for (aTile = 0; aTile < numTiles; aTile++) {
	      posNext = TileNFromSpace(w, plunger, aTile + 1, dir);
	      /* Calculate deltas */
	      dx = CartesianX(w, posNext);
	      dy = CartesianY(w, posNext);
	      ix = ((dir == RIGHT) ? inc : -inc);
	      iy = 0;
	      DrawTile(w, posNext, False, False, FALSE, ix, iy);
	      /* Erase old slivers */
	      ix += dx;
	      iy += dy;
	      if (aTile < numTiles - 1) {
		switch (dir) {
		case RIGHT:
		  FILLRECTANGLE(w, dr,
		    w->barrel.inverseGC,
		    ix - gapI, iy, gapI, w->barrel.tileSize.y);
		  break;
		case LEFT:
		  FILLRECTANGLE(w, dr,
		    w->barrel.inverseGC,
		    ix + w->barrel.tileSize.x, iy,
		    gapI, w->barrel.tileSize.y);
		  break;
		}
	      } else {
		switch (dir) {
		case RIGHT:
		  FILLRECTANGLE(w, dr,
		    w->barrel.inverseGC,
		    fillBeginI, iy,
		    ix - fillBeginI, w->barrel.tileSize.y);
		  break;
		case LEFT:
		  FILLRECTANGLE(w, dr,
		    w->barrel.inverseGC,
		    ix + w->barrel.tileSize.x, iy,
		    fillBeginI - ix, w->barrel.tileSize.y);
		  break;
		}
	      }
	    }
	    useTimer(w, w->barrel.delay / fast);
	  }
	}
	for (plunger = 0; plunger < NUMPLUNGERS; plunger++) {
	  pos = w->barrel.spacePosition[plunger];
	  for (aTile = 0; aTile < numTiles; aTile++) {
	    posNext = TileNFromSpace(w, plunger, aTile + 1, dir);
	    w->barrel.tileOfPosition[pos] =
	      w->barrel.tileOfPosition[posNext];
	    pos = posNext;
	  }
	  w->barrel.spacePosition[plunger] = fillBeginPos[plunger];
	  w->barrel.tileOfPosition[fillBeginPos[plunger]] = -NUMPLUNGERS +
		  plunger;
	}
}

static void
DrawAllTilesForColumns(BarrelWidget w, int start, int finish)
{
	int i, j, k;

	for (i = start; i <= finish; i++) {
		for (j = 0; j < w->barrel.faces; j++) {
			k = j * w->barrel.tiles + i;
			if (w->barrel.tileOfPosition[k] > 0)
			  DrawTile(w, k,
			    (w->barrel.tileOfPosition[k] < 0),
			    (w->barrel.tileOfPosition[k] < 0), FALSE, 0, 0);
		}
	}
}

static void
AnimateRotate(BarrelWidget w, int start, int finish, int direction, int fast)
{
#ifdef WINVER
	HDC memDC;
	HBITMAP hBit;
#else
	Pixmap bufferBox;
	XWindowAttributes xgwa;
#endif
	int size = finish - start + 1;
	int moveI, gapJ, moveJ, inc, i;

	moveI = w->barrel.tileSize.x + w->barrel.delta.x;
	gapJ = w->barrel.tileSize.y * fast / w->barrel.numSlices;
	moveJ = w->barrel.tileSize.y + w->barrel.delta.y + 2;
	if (gapJ == 0)
		gapJ++;

#ifdef WINVER
	memDC = CreateCompatibleDC(w->core.hDC);
	hBit = CreateCompatibleBitmap(w->core.hDC, size * moveI + 1,
		w->barrel.puzzleSize.y);
	SelectObject(memDC, hBit);
	BitBlt(memDC, 0, 0,
		size * moveI,
		w->barrel.puzzleSize.y,
		w->core.hDC,
		start * moveI + w->barrel.puzzleOffset.x + 1,
		w->barrel.puzzleOffset.y,
		SRCCOPY);
#else
	(void) XGetWindowAttributes(XtDisplay(w), XtWindow(w), &xgwa);

	if ((bufferBox = XCreatePixmap(XtDisplay(w), XtWindow(w),
			size * moveI,
			w->barrel.puzzleSize.y,
			xgwa.depth)) == None) {
		XtError("Not enough memory, exiting.");
	}
	XCopyArea(XtDisplay(w), XtWindow(w), bufferBox,
		w->barrel.frameGC,
		start * moveI + w->barrel.puzzleOffset.x + 1,
		w->barrel.puzzleOffset.y,
		size * moveI,
		w->barrel.puzzleSize.y,
		0, 0);
	XSetGraphicsExposures(XtDisplay(w), w->barrel.frameGC, False);
#endif
	initTimer(w);
	for (inc = 0; inc < moveJ + gapJ; inc += gapJ) {
		if (inc > moveJ)
			inc = moveJ;
		if (direction == TOP)
			i = inc;
		else
			i = w->barrel.puzzleSize.y - inc + 1;
#ifdef WINVER
		SelectObject(memDC, hBit);
		BitBlt(w->core.hDC,
			start * moveI + w->barrel.puzzleOffset.x + 1,
			w->barrel.puzzleOffset.y + 1,
			size * moveI,
			w->barrel.puzzleSize.y - i,
			memDC,
			0, i,
			SRCCOPY);
		BitBlt(w->core.hDC,
			start * moveI + w->barrel.puzzleOffset.x + 1,
			w->barrel.puzzleSize.y - i + w->barrel.puzzleOffset.y -
			w->barrel.delta.y + 1,
			size * moveI,
			i - w->barrel.delta.y,
			memDC,
			0, 0,
			SRCCOPY);
#else

		XCopyArea(XtDisplay(w), bufferBox, XtWindow(w),
			w->barrel.frameGC,
			0, i,
			size * moveI,
			w->barrel.puzzleSize.y - i,
			start * moveI + w->barrel.puzzleOffset.x + 1,
			w->barrel.puzzleOffset.y + 1);
		XCopyArea(XtDisplay(w), bufferBox, XtWindow(w),
			w->barrel.frameGC,
			0, 0,
			size * moveI,
			i - w->barrel.delta.y,
			start * moveI + w->barrel.puzzleOffset.x + 1,
			w->barrel.puzzleSize.y - i + w->barrel.puzzleOffset.y -
			w->barrel.delta.y + 1);
#endif
		useTimer(w, w->barrel.delay / fast);
	}
#ifdef WINVER
	DeleteObject(hBit);
	DeleteDC(memDC);
#else
	XFreePixmap(XtDisplay(w), bufferBox);
#endif
}

static void
EraseFrame(const BarrelWidget w, Pixmap dr)
{
	FILLRECTANGLE(w, dr, w->barrel.inverseGC,
		0, 0, w->core.width, w->core.height);
}

static void
DrawFrame(BarrelWidget w, Pixmap dr, Boolean focus)
{
	int sumX, sumY, offsetX, offsetY, k;
	GC gc = (focus) ? w->barrel.frameGC : w->barrel.borderGC;

	offsetX = w->barrel.puzzleOffset.x;
	offsetY = w->barrel.puzzleOffset.y;
	FILLRECTANGLE(w, dr, w->barrel.borderGC,
		0, 0, w->core.width, offsetY);
	FILLRECTANGLE(w, dr, w->barrel.borderGC,
		0, w->core.height - offsetY,
		w->core.width, offsetY);
	FILLRECTANGLE(w, dr, w->barrel.borderGC,
		0, 0, offsetX, w->core.height);
	FILLRECTANGLE(w, dr, w->barrel.borderGC,
		w->core.width - offsetX, 0,
		offsetX, w->core.height);

	sumX = w->barrel.tiles * w->barrel.offset.x + 1;
	sumY = w->barrel.offset.y + 1;
	for (k = 0; k < w->barrel.faces; k++) {
		FILLRECTANGLE(w, dr, gc,
			offsetX, offsetY, 1, sumY);
		FILLRECTANGLE(w, dr, gc,
			offsetX, offsetY, sumX, 1);
		FILLRECTANGLE(w, dr, gc,
			sumX + offsetX, offsetY, 1, sumY + 1);
		offsetY += sumY;
		FILLRECTANGLE(w, dr, gc,
			offsetX, offsetY, sumX + 1, 1);
	}
}

static void
MoveNoTiles(BarrelWidget w)
{
	SetBarrel(w, BARREL_IGNORE);
}

static void
MoveTiles(BarrelWidget w, int from, int fast)
{
	w->barrel.currentRef = from;
	if (fast != INSTANT && w->barrel.delay > 0) {
		if (w->barrel.spacePosition[0] % w->barrel.tiles == 0) {
			AnimateSlide(w, w->barrel.tiles - 1, LEFT, fast);
		} else {
			AnimateSlide(w, w->barrel.tiles - 1, RIGHT, fast);
		}
	} else {
		int pos, posNext, plunger, aTile, dir;

		dir = (w->barrel.spacePosition[0] % w->barrel.tiles == 0) ?
			LEFT : RIGHT;
		for (plunger = 0; plunger < NUMPLUNGERS; plunger++) {
			pos = w->barrel.spacePosition[plunger];
			for (aTile = 0; aTile < w->barrel.tiles - 1; aTile++) {
				posNext = TileNFromSpace(w, plunger, 1, dir);
				w->barrel.tileOfPosition[pos] =
					w->barrel.tileOfPosition[posNext];
				DrawTile(w, pos, False, False, FALSE, 0, 0);
				pos = posNext;
				w->barrel.spacePosition[plunger] = pos;
			}
			w->barrel.tileOfPosition[pos] = -NUMPLUNGERS + plunger;
			DrawTile(w, pos, True, True, FALSE, 0, 0);
		}
	}
	w->barrel.currentRef = -w->barrel.tileFaces;
#ifdef USE_SOUND
	if (w->barrel.sound) {
		playSound((char *) BUMPSOUND);
	}
#endif
}

static void
RotateTiles(BarrelWidget w, int start, int finish, int direction, int fast)
{
	int newR, i, k, tempTile, currTile, pos, newPos;
	int r = 0, plunger;
	Boolean	animate = (fast != INSTANT && w->barrel.delay > 0);

	if (animate)
		AnimateRotate(w, start, finish, direction, fast);
	for (i = start; i <= finish; i++) {
		currTile = w->barrel.tileOfPosition[r * w->barrel.tiles + i];
		for (k = 1; k <= w->barrel.faces; k++) {
			newR = (direction == TOP) ? (r +
				w->barrel.faces - 1) % w->barrel.faces :
				(r + 1) % w->barrel.faces;
			pos = r * w->barrel.tiles + i;
			newPos = newR * w->barrel.tiles + i;
			tempTile = w->barrel.tileOfPosition[newPos];
			w->barrel.tileOfPosition[newPos] = currTile;
			currTile = tempTile;
			for (plunger = 0; plunger < NUMPLUNGERS; plunger++) {
				if (w->barrel.spacePosition[plunger] == pos) {
					if (!animate)
						DrawTile(w, newPos, True, True,
							FALSE, 0, 0);
					break;
				}
			}
			if (!animate && plunger == NUMPLUNGERS)
				DrawTile(w, newPos, False, False,
					FALSE, 0, 0);
			r = newR;
		}
	}
	if (start == 0) {
		for (plunger = 0; plunger < NUMPLUNGERS; plunger++) {
			i = (direction == TOP) ? w->barrel.faces - 1 : 1;
			w->barrel.spacePosition[plunger] =
				(w->barrel.spacePosition[plunger] + i *
					w->barrel.tiles) % w->barrel.tileFaces;
		}
	}
	/* Would be nice if this was not needed. */
#ifndef DEBUG
	if (animate) {
		int moveI = w->barrel.tileSize.x + w->barrel.delta.x;
		Pixmap dr = 0;

		FILLRECTANGLE(w, dr, w->barrel.inverseGC,
			start * moveI + w->barrel.puzzleOffset.x + 1, 0,
			(finish - start + 1) * moveI, w->core.height);
		DrawFrame(w, dr, w->barrel.focus);
		DrawAllTilesForColumns(w, start, finish);
	}
#endif
#ifdef USE_SOUND
	if (w->barrel.sound) {
		playSound((char *) MOVESOUND);
	}
#endif
}

static Boolean
MoveTilesDir(BarrelWidget w, int direction, int tile, Boolean all, int fast)
{
	int start, finish;

	start = tile;
	finish = tile;
	if (all) {
		start = 0;
		finish = w->barrel.tiles - 1;
	} else if (w->barrel.pairs && tile > 0 && tile < w->barrel.tiles - 1) {
		start = 1 + ((tile - 1) / 2) * 2;
		finish = start + 1;
	}
	switch (direction) {
	case TOP:
		RotateTiles(w, start, finish, TOP, fast);
		return True;
	case RIGHT:
		if (Column(w, w->barrel.spacePosition[0]) > 0) {
			MoveTiles(w, 0, fast);
			return True;
		}
		break;
	case BOTTOM:
		RotateTiles(w, start, finish, BOTTOM, fast);
		return True;
	case LEFT:
		if (Column(w, w->barrel.spacePosition[0]) < w->barrel.tiles - 1) {
			MoveTiles(w, w->barrel.tiles - 1, fast);
			return True;
		}
		break;
	default:
		{
			char *buf;

			intCat(&buf, "MoveTilesDir: direction ", direction);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
	return False;
}

static void
MoveShift(BarrelWidget w, int direction, int fast)
{
	(void) MoveTilesDir(w, direction, 0, True, fast);
	SetBarrel(w, BARREL_CONTROL);
}

#if 0
static void
ControlBarrel(BarrelWidget w, int direction)
{
	int reason = BARREL_IGNORE;

	switch (direction) {
		case TOP:
			if (w->barrel.faces <= MINFACES)
				return;
			reason = BARREL_DEC_Y;
			break;
		case RIGHT:
			reason = BARREL_INC_X;
			break;
		case BOTTOM:
			if (w->barrel.faces >= MAXFACES)
				return;
			reason = BARREL_INC_Y;
			break;
		case LEFT:
			if (w->barrel.tiles <= MINTILES)
				return;
			reason = BARREL_DEC_X;
			break;
		default:
			{
				char *buf;

				intCat(&buf, "ControlBarrel: direction ",
					direction);
				DISPLAY_WARNING(buf);
				free(buf);
			}
	}
	SetBarrel(w, reason);
}
#endif

Boolean
MoveBarrel(BarrelWidget w, const int direction, const int tile,
		const int shift, const Boolean motion, const int fast)
{
	if (shift != 0 && (direction == TOP || direction == BOTTOM)) {
		MoveShift(w, direction, fast);
		if (!motion) {
			PutMove(direction, tile, 1);
		}
		return True;
	} else if (MoveTilesDir(w, direction, tile, False, fast)) {
		if (!motion) {
			SetBarrel(w, BARREL_MOVED);
			PutMove(direction, tile, 0);
		}
		return True;
	}
	return False;
}

static void
DiffMove(BarrelWidget w, int diff, int j, int shift)
{
	int k;

	if (diff > w->barrel.faces / 2 ||
	    (diff == (w->barrel.faces + 1) / 2 && j > w->barrel.currentFace))
		for (k = 0; k < w->barrel.faces - diff; k++)
			(void) MoveBarrel(w, BOTTOM, w->barrel.currentTile,
				shift, True, NORMAL);
	else
		for (k = 0; k < diff; k++)
			(void) MoveBarrel(w, TOP, w->barrel.currentTile,
				shift, True, NORMAL);
}

static void
MoveRecord(BarrelWidget w, const int direction, const int tile, const int shift)
{
	if (shift != 0 && (direction == TOP || direction == BOTTOM)) {
		PutMove(direction, tile, 1);
	} else {
		SetBarrel(w, BARREL_MOVED);
		PutMove(direction, tile, 0);
	}
}

static void
DiffRecord(BarrelWidget w, int diff, int j, int shift)
{
	int k;

	if (diff > w->barrel.faces / 2 ||
	    (diff == (w->barrel.faces + 1) / 2 && j > w->barrel.currentFace))
		for (k = 0; k < w->barrel.faces - diff; k++)
			(void) MoveRecord(w, BOTTOM,
				w->barrel.currentTile, shift);
	else
		for (k = 0; k < diff; k++)
			(void) MoveRecord(w, TOP,
				w->barrel.currentTile, shift);
}

void
MoveBarrelDelay(BarrelWidget w, const int direction, const int tile,
		const Boolean all)
{
	if (direction == RIGHT) {
		PutMove(direction, 0, False);
	} else if (direction == LEFT) {
		PutMove(direction, w->barrel.tiles - 1, False);
	} else {
		SetBarrel(w, BARREL_MOVED);
		PutMove(direction, tile, all);
	}
	(void) MoveTilesDir(w, direction, tile, all, NORMAL);
	Sleep((unsigned int) w->barrel.delay);
}

static void
SlideBarrel(BarrelWidget w, int direction)
{
	if (CheckSolved(w) && !w->barrel.practice) {
		MoveNoTiles(w);
		return;
	}
	if (!MoveBarrel(w, direction, 0, FALSE, False, NORMAL)) {
		SetBarrel(w, BARREL_BLOCKED);
		return;
	}
	if (CheckSolved(w)) {
		SetBarrel(w, BARREL_SOLVED);
	}
	return;
}

static void
RotateBarrel(BarrelWidget w, int direction, int tile)
{
	if (CheckSolved(w) && !w->barrel.practice) {
		MoveNoTiles(w);
		return;
	}
	(void) MoveBarrel(w, direction, tile, FALSE, False, NORMAL);
	if (CheckSolved(w)) {
		SetBarrel(w, BARREL_SOLVED);
	}
	return;
}

#ifndef WINVER
static
#endif
void
MoveBarrelInput(BarrelWidget w, int x, int direction, int shift)
{
	int r;

	if (shift != 0 && (direction == TOP || direction == BOTTOM)) {
		MoveShift(w, direction, NORMAL);
		PutMove(direction, 0, 1);
	} else if (CheckSolved(w) && !w->barrel.practice) {
		MoveNoTiles(w);
		return;
	} else if (direction == LEFT || direction == RIGHT) {
		SlideBarrel(w, direction);
		if (CheckSolved(w)) {
			SetBarrel(w, BARREL_SOLVED);
		}
	} else {
		if (!PositionTile(w, x, &r))
			return;
		if (r == 0 || r == w->barrel.tiles - 1) {
			SetBarrel(w, BARREL_BLOCKED);
			return;
		}
		RotateBarrel(w, direction, r);
		if (CheckSolved(w)) {
			SetBarrel(w, BARREL_SOLVED);
		}
	}
}

static int
SelectTiles(BarrelWidget w, int x, int y, int *i, int *j)
{
	int temp;

	*i = (x - w->barrel.delta.x / 2 - w->barrel.puzzleOffset.x) /
		w->barrel.offset.x;
	temp = w->barrel.faces * w->barrel.offset.y + w->barrel.delta.y - 1;
	*j = ((y - w->barrel.delta.y / 2 - w->barrel.puzzleOffset.y + temp) %
	      temp) / w->barrel.offset.y;
	if (*i >= 0 && *j >= 0 &&
	    *i < w->barrel.tiles && *j < w->barrel.faces) {
		return (*i + w->barrel.tiles * *j -
			w->barrel.spacePosition[0] % w->barrel.tileFaces);
	}
	return -w->barrel.tileFaces;
}

static void
SelectSlideTiles(BarrelWidget w, int pos)
{
	if (pos < 0) {
		if (w->barrel.delay > 0) {
			AnimateSlide(w, -pos, RIGHT, NORMAL);
#ifdef USE_SOUND
			if (w->barrel.sound) {
				playSound((char *) BUMPSOUND);
			}
#endif
		} else {
			MoveTiles(w, w->barrel.spacePosition[0] % w->barrel.tiles - w->barrel.tiles + 1,
				NORMAL);
		}
		SetBarrel(w, BARREL_MOVED);
		PutMove(RIGHT, 0, 0);
	} else if (pos > 0) {
		if (w->barrel.delay > 0) {
			AnimateSlide(w, pos, LEFT, NORMAL);
#ifdef USE_SOUND
			if (w->barrel.sound) {
				playSound((char *) BUMPSOUND);
			}
#endif
		} else {
			MoveTiles(w, w->barrel.spacePosition[0] % w->barrel.tiles + w->barrel.tiles - 1,
				NORMAL);
		}
		SetBarrel(w, BARREL_MOVED);
		PutMove(LEFT, w->barrel.tiles - 1, 0);
	}
}

static void
ResetTiles(BarrelWidget w)
{
	int i, inc;

	w->barrel.tileFaces = w->barrel.tiles * w->barrel.faces;
	if (w->barrel.tileOfPosition)
		free(w->barrel.tileOfPosition);
	if (!(w->barrel.tileOfPosition = (int *)
			malloc(sizeof (int) * w->barrel.tileFaces))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	if (startPosition)
		free(startPosition);
	if (!(startPosition = (int *)
			malloc(sizeof (int) * w->barrel.tileFaces))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}

	inc = 0;
	for (i = 0; i < w->barrel.tileFaces; i++) {
		if (i % w->barrel.tiles != 0 &&
				i % w->barrel.tiles != w->barrel.tiles - 1) {
			inc++;
			w->barrel.tileOfPosition[i] = inc;
		} else {
			w->barrel.tileOfPosition[i] = 0;
		}
	}
	for (i = 0; i < NUMPLUNGERS; i++) {
		w->barrel.spacePosition[i] = 2 * i * w->barrel.tiles;
		w->barrel.tileOfPosition[2 * i * w->barrel.tiles] =
			-NUMPLUNGERS + i;
		inc++;
		w->barrel.tileOfPosition[(2 * i + 1) * w->barrel.tiles - 1] =
			inc;
	}
	inc++;
	for (i = 0; i < w->barrel.faces - NUMPLUNGERS; i++) {
		w->barrel.tileOfPosition[(2 * i + 1) * w->barrel.tiles] =
			inc++;
		w->barrel.tileOfPosition[(2 * i + 2) * w->barrel.tiles - 1] =
			inc++;
	}
	FlushMoves(w);
	w->barrel.currentRef = -w->barrel.tileFaces;
	w->barrel.started = False;
}

static void
GetTiles(BarrelWidget w)
{
	FILE *fp;
	int c, pairs, 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 (get) ", 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 (get) ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
		}
#endif
	}
	FlushMoves(w);
#if 0
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &tiles);
	if (tiles >= MINTILES) {
		for (i = w->barrel.tiles; i < tiles; i++) {
			SetBarrel(w, BARREL_INC_X);
		}
		for (i = w->barrel.tiles; i > tiles; i--) {
			SetBarrel(w, BARREL_DEC_X);
		}
	} else {
		stringCat(&buf1, name, " corrupted: tiles ");
		intCat(&buf2, buf1, tiles);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MINTILES);
		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", &faces);
	if (faces >= MINFACES && faces <= MAXFACES) {
		for (i = w->barrel.faces; i < faces; i++) {
			SetBarrel(w, BARREL_INC_Y);
		}
		for (i = w->barrel.faces; i > faces; i--) {
			SetBarrel(w, BARREL_DEC_Y);
		}
	} else {
		stringCat(&buf1, name, " corrupted: faces ");
		intCat(&buf2, buf1, faces);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MINFACES);
		free(buf1);
		stringCat(&buf1, buf2, " and ");
		free(buf2);
		intCat(&buf2, buf1, MAXFACES);
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
	}
#endif
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &pairs);
	if (w->barrel.pairs != (Boolean) pairs) {
		SetBarrel(w, BARREL_PAIRS);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &orient);
	if (w->barrel.orient != (Boolean) orient) {
		SetBarrel(w, BARREL_ORIENT);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &practice);
	if (w->barrel.orient != (Boolean) practice) {
		SetBarrel(w, BARREL_PRACTICE);
	}
#ifdef WINVER
	ResetTiles(w);
#endif
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	(void) fscanf(fp, "%d", &moves);
	ScanStartPosition(fp, w);
	SetBarrel(w, BARREL_RESTORE);
	SetStartPosition(w);
	ScanMoves(fp, w, moves);
	(void) fclose(fp);
	(void) printf("%s: pairs %d, orient %d, practice %d, moves %d.\n",
		name, pairs, orient, practice, moves);
	free(lname);
	free(fname);
	w->barrel.cheat = True; /* Assume the worst. */
}

static void
WriteTiles(BarrelWidget 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
	}
#if 0
	(void) fprintf(fp, "tiles%c %d\n", SYMBOL, w->barrel.tiles);
	(void) fprintf(fp, "faces%c %d\n", SYMBOL, w->barrel.faces);
#endif
	(void) fprintf(fp, "pairs%c %d\n", SYMBOL,
		(w->barrel.pairs) ? 1 : 0);
	(void) fprintf(fp, "orient%c %d\n", SYMBOL,
		(w->barrel.orient) ? 1 : 0);
	(void) fprintf(fp, "practice%c %d\n", SYMBOL,
		(w->barrel.practice) ? 1 : 0);
	(void) fprintf(fp, "moves%c %d\n", SYMBOL, NumMoves());
	PrintStartPosition(fp, w);
	PrintMoves(fp);
	(void) fclose(fp);
	(void) printf("Saved to %s.\n", name);
	free(lname);
	free(fname);
}

static void
ClearTiles(BarrelWidget w)
{
	if (w->barrel.currentRef != -w->barrel.tileFaces)
		return;
	ResetTiles(w);
	DrawAllTiles(w);
	SetBarrel(w, BARREL_RESET);
}

static void
UndoTiles(BarrelWidget w)
{
	if (MadeMoves() && w->barrel.currentRef == -w->barrel.tileFaces) {
		int direction, tile, shift;

		GetMove(&direction, &tile, &shift);
		direction = (direction + COORD / 2) % COORD;
		if (shift != 0 && (direction == TOP || direction == BOTTOM)) {
			MoveShift(w, direction, DOUBLE);
		} else if (MoveTilesDir(w, direction, tile, False, DOUBLE)) {
			SetBarrel(w, BARREL_UNDO);
		} else {
			char *buf1, *buf2;

			intCat(&buf1, "Move ", direction);
			stringCat(&buf2, buf1, " can not be made");
			free(buf1);
			DISPLAY_WARNING(buf2);
			free(buf2);
		}
	}
}

static void
PracticeTiles(BarrelWidget w)
{
	SetBarrel(w, BARREL_PRACTICE);
}

static void
RandomizeTiles(BarrelWidget w)
{
	if (w->barrel.currentRef != -w->barrel.tileFaces)
		return;
	if (w->barrel.practice)
		PracticeTiles(w);
	SetBarrel(w, BARREL_RESET);
	w->barrel.cheat = False;
	/* Now move the space around randomly */
	if (w->barrel.tiles > 1 || w->barrel.faces > 1) {
		int big = w->barrel.tileFaces + NRAND(4);
		int lastDirection = -1;
		int randomDirection;

		if (big > 100)
			big = 100;
#ifdef DEBUG
		big = 3;
#endif
		while (big-- > 0) {
			randomDirection = NRAND(COORD);
			if ((randomDirection & 1) == 1) {
				if (w->barrel.spacePosition[0] % w->barrel.tiles == 0)
					randomDirection = LEFT;
				else
					randomDirection = RIGHT;
			}
			if ((randomDirection + COORD / 2) % COORD != lastDirection) {
				if ((randomDirection & 1) == 1) {
					(void) MoveBarrel(w, randomDirection,
						w->barrel.tiles - 1, FALSE,
						False, INSTANT);
				} else {
					(void) MoveBarrel(w, randomDirection,
						NRAND(w->barrel.tiles - 2) + 1,
						FALSE, False, INSTANT);
				}
#ifdef DEBUG
				sleep(1);
#endif
			} else {
				big++;
			}
		}
		FlushMoves(w);
		SetBarrel(w, BARREL_RANDOMIZE);
	}
	if (CheckSolved(w)) {
		SetBarrel(w, BARREL_SOLVED);
	}
}

static void
SolveTiles(BarrelWidget w)
{
	if (CheckSolved(w) || w->barrel.currentRef != -w->barrel.tileFaces)
		return;
	/* if (w->barrel.tiles == 6 && w->barrel.faces == 5) */
#ifdef AUTOSOLVE
	SolveSomeTiles(w);
#else
	SetBarrel(w, BARREL_SOLVE_MESSAGE);
#endif
}

static void
OrientizeTiles(BarrelWidget w)
{
	SetBarrel(w, BARREL_ORIENT);
}

static void
PairsTiles(BarrelWidget w)
{
	SetBarrel(w, BARREL_PAIRS);
}

static void
SpeedTiles(BarrelWidget w)
{
	w->barrel.delay -= 5;
	if (w->barrel.delay < 0)
		w->barrel.delay = 0;
#ifdef HAVE_MOTIF
	SetBarrel(w, BARREL_SPEED);
#endif
}

static void
SlowTiles(BarrelWidget w)
{
	w->barrel.delay += 5;
#ifdef HAVE_MOTIF
	SetBarrel(w, BARREL_SPEED);
#endif
}

static void
SoundTiles(BarrelWidget w)
{
	w->barrel.sound = !w->barrel.sound;
}

#define FACTOR 0.7
#ifdef WINVER
#define MAXINTENSITY 0xFF
static int
brighter(const int light)
{
	int i = (int) ((1 - FACTOR) * MAXINTENSITY);
	int temp = light;

	if (temp < i)
		temp = i;
	return MIN(temp / FACTOR, MAXINTENSITY);
}

static int
darker(const int light)
{
	return (int) (light * FACTOR);
}

static void
SetValuesBarrel(BarrelWidget w)
{
	struct tagColor {
		int red, green, blue;
	} color;
	char szBuf[80], buf[20], charbuf[2];
	int face;

	w->barrel.tiles = GetPrivateProfileInt(SECTION, "tiles",
		DEFAULTTILES, INIFILE);
	w->barrel.faces = GetPrivateProfileInt(SECTION, "faces",
		DEFAULTFACES, INIFILE);
	w->barrel.orient = (BOOL) GetPrivateProfileInt(SECTION, "orient",
		FALSE, INIFILE);
	w->barrel.pairs = (BOOL) GetPrivateProfileInt(SECTION, "pairs",
		TRUE, INIFILE);
	w->barrel.base = GetPrivateProfileInt(SECTION, "base",
		DEFAULTBASE, INIFILE);
	w->barrel.mono = (BOOL) GetPrivateProfileInt(SECTION, "mono",
		DEFAULTMONO, INIFILE);
	w->barrel.reverse = (BOOL) GetPrivateProfileInt(SECTION, "reverse",
		DEFAULTREVERSE, INIFILE);
	/* cyan */
	(void) GetPrivateProfileString(SECTION, "frameColor", "0 255 255",
		szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->barrel.frameGC = RGB(color.red, color.green, color.blue);
	/* gray75 */
	(void) GetPrivateProfileString(SECTION, "tileColor", "191 191 191",
		szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->barrel.tileGC = RGB(color.red, color.green, color.blue);
	/* gray50 */
	(void) GetPrivateProfileString(SECTION, "tileBorder", "127 127 127",
		szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->barrel.tileBrighterGC = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->barrel.tileDarkerGC = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	w->barrel.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->barrel.inverseGC = RGB(color.red, color.green, color.blue);
	for (face = 0; face < MAXCOLORS; face++) {
		(void) sprintf(buf, "faceColor%d", face);
		(void) GetPrivateProfileString(SECTION,
			buf, faceColorString[face],
			szBuf, sizeof (szBuf), INIFILE);
		(void) sscanf(szBuf, "%d %d %d",
			&(color.red), &(color.green), &(color.blue));
		w->barrel.faceGC[face] =
			RGB(color.red, color.green, color.blue);
		(void) sprintf(buf, "faceChar%d", face);
		charbuf[0] = faceColorChar[face];
		charbuf[1] = '\0';
		(void) GetPrivateProfileString(SECTION, buf, charbuf,
			szBuf, sizeof (szBuf), INIFILE);
		w->barrel.faceChar[face] = szBuf[0];
	}
	(void) GetPrivateProfileString(SECTION, "picture", PICTURE,
		szBuf, sizeof (szBuf), INIFILE);
	(void) strcpy(w->barrel.picture, szBuf);
	w->barrel.picture[80] = 0;
	w->barrel.delay = GetPrivateProfileInt(SECTION, "delay",
		DEFAULTDELAY, INIFILE);
	w->barrel.sound = (BOOL)
		GetPrivateProfileInt(SECTION, "sound", 0, INIFILE);
	(void) GetPrivateProfileString(SECTION, "bumpSound", BUMPSOUND,
		szBuf, sizeof (szBuf), INIFILE);
	w->barrel.bumpSound[80] = 0;
	(void) strcpy(w->barrel.bumpSound, szBuf);
	(void) GetPrivateProfileString(SECTION, "moveSound", MOVESOUND,
		szBuf, sizeof (szBuf), INIFILE);
	(void) strcpy(w->barrel.moveSound, szBuf);
	w->barrel.moveSound[80] = 0;
	(void) GetPrivateProfileString(SECTION, "name", "Guest",
			szBuf, sizeof (szBuf), INIFILE);
	(void) strcpy(w->barrel.username, szBuf);
	w->barrel.username[80] = 0;
}

void
DestroyBarrel(HBRUSH brush)
{
	(void) DeleteObject(brush);
	PostQuitMessage(0);
}

#else
#define MAXINTENSITY 0xFFFF

static Pixel
brighter(BarrelWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = (w->barrel.colormap == None) ?
		w->barrel.oldColormap : w->barrel.colormap;
	int i = (int) ((1 - FACTOR) * MAXINTENSITY);

	color.pixel = pixel;
	XQueryColor(XtDisplay(w), colormap, &color);
	if (color.red < i)
		color.red = i;
	if (color.green < i)
		color.green = i;
	if (color.blue < i)
		color.blue = i;
	color.red = (unsigned short) MIN(color.red / FACTOR, MAXINTENSITY);
	color.green = (unsigned short) MIN(color.green / FACTOR, MAXINTENSITY);
	color.blue = (unsigned short) MIN(color.blue / FACTOR, MAXINTENSITY);
	if (XAllocColor(XtDisplay(w), colormap, &color))
		return color.pixel;
	return pixel;
}

static Pixel
darker(BarrelWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = (w->barrel.colormap == None) ?
		w->barrel.oldColormap : w->barrel.colormap;

	color.pixel = pixel;
	XQueryColor(XtDisplay(w), colormap, &color);
	color.red = (unsigned short) (color.red * FACTOR);
	color.green = (unsigned short) (color.green * FACTOR);
	color.blue = (unsigned short) (color.blue * FACTOR);
	if (XAllocColor(XtDisplay(w), colormap, &color))
		return color.pixel;
	return pixel;
}

static void
GetColor(BarrelWidget w, int face)
{
	XGCValues values;
	XtGCMask valueMask;
	XColor colorCell, rgb;
	Colormap colormap = (w->barrel.colormap == None) ?
		w->barrel.oldColormap : w->barrel.colormap;

	valueMask = GCForeground | GCBackground;
	if (w->barrel.reverse) {
		values.background = w->barrel.foreground;
	} else {
		values.background = w->barrel.background;
	}
	if (!w->barrel.mono) {
		if (XAllocNamedColor(XtDisplay(w), colormap,
				w->barrel.faceName[face], &colorCell, &rgb)) {
			values.foreground = w->barrel.faceColor[face] = colorCell.pixel;
			if (w->barrel.faceGC[face])
				XtReleaseGC((Widget) w, w->barrel.faceGC[face]);
			w->barrel.faceGC[face] = XtGetGC((Widget) w, valueMask, &values);
			return;
		} else {
			char *buf1, *buf2;

			stringCat(&buf1, "Color name \"",
				w->barrel.faceName[face]);
			stringCat(&buf2, buf1, "\" is not defined for face ");
			free(buf1);
			intCat(&buf1, buf2, face);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
		}
	}
	if (w->barrel.reverse) {
		values.background = w->barrel.background;
		values.foreground = w->barrel.foreground;
	} else {
		values.background = w->barrel.foreground;
		values.foreground = w->barrel.background;
	}
	if (w->barrel.faceGC[face])
		XtReleaseGC((Widget) w, w->barrel.faceGC[face]);
	w->barrel.faceGC[face] = XtGetGC((Widget) w, valueMask, &values);
}

static void
SetAllColors(BarrelWidget w)
{
	XGCValues values;
	XtGCMask valueMask;
	int face;

	valueMask = GCForeground | GCBackground;

	if (w->barrel.reverse) {
		values.foreground = w->barrel.foreground;
		values.background = w->barrel.background;
	} else {
		values.foreground = w->barrel.background;
		values.background = w->barrel.foreground;
	}
	if (w->barrel.inverseGC)
		XtReleaseGC((Widget) w, w->barrel.inverseGC);
	w->barrel.inverseGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->barrel.mono) {
		if (w->barrel.reverse) {
			values.foreground = w->barrel.background;
			values.background = w->barrel.foreground;
		} else {
			values.foreground = w->barrel.foreground;
			values.background = w->barrel.background;
		}
	} else {
		values.foreground = w->barrel.frameColor;
		values.background = w->barrel.borderColor;
	}
	if (w->barrel.frameGC)
		XtReleaseGC((Widget) w, w->barrel.frameGC);
	w->barrel.frameGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->barrel.mono) {
		if (w->barrel.reverse) {
			values.background = w->barrel.foreground;
			values.foreground = w->barrel.background;
		} else {
			values.foreground = w->barrel.foreground;
			values.background = w->barrel.background;
		}
	} else {
		values.foreground = w->barrel.tileColor;
		values.background = w->barrel.borderColor;
	}
	if (w->barrel.tileGC)
		XtReleaseGC((Widget) w, w->barrel.tileGC);
	w->barrel.tileGC = XtGetGC((Widget) w, valueMask, &values);
	if (!w->barrel.mono) {
		values.foreground = darker(w, w->barrel.tileColor);
	}
	if (w->barrel.tileBrighterGC)
		XtReleaseGC((Widget) w, w->barrel.tileBrighterGC);
	w->barrel.tileBrighterGC = XtGetGC((Widget) w, valueMask, &values);
	if (!w->barrel.mono) {
		values.foreground = brighter(w, w->barrel.tileColor);
	}
	if (w->barrel.tileDarkerGC)
		XtReleaseGC((Widget) w, w->barrel.tileDarkerGC);
	w->barrel.tileDarkerGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->barrel.mono) {
		if (w->barrel.reverse) {
			values.foreground = w->barrel.foreground;
			values.background = w->barrel.background;
		} else {
			values.background = w->barrel.foreground;
			values.foreground = w->barrel.background;
		}
	} else {
		values.foreground = w->barrel.borderColor;
		values.background = w->barrel.tileColor;
	}
	if (w->barrel.borderGC)
		XtReleaseGC((Widget) w, w->barrel.borderGC);
	w->barrel.borderGC = XtGetGC((Widget) w, valueMask, &values);
	for (face = 0; face < MAXCOLORS; face++)
		GetColor(w, face);
	if (w->barrel.fontInfo) {
		XSetFont(XtDisplay(w), w->barrel.tileGC,
			w->barrel.fontInfo->fid);
	}
}

static Boolean
SetValuesBarrel(Widget current, Widget request, Widget renew)
{
	BarrelWidget c = (BarrelWidget) current, w = (BarrelWidget) renew;
	Boolean redraw = False, setColors = False;
	Boolean redrawTiles = False;
	int face;

	CheckTiles(w);
	for (face = 0; face < MAXFACES; face++) {
		if (strcmp(w->barrel.faceName[face],
				c->barrel.faceName[face])) {
			setColors = True;
			break;
		}
	}
	if (w->barrel.font != c->barrel.font ||
	    w->barrel.tileColor != c->barrel.tileColor ||
	    w->barrel.reverse != c->barrel.reverse ||
	    w->barrel.mono != c->barrel.mono) {
		loadFont(w);
		SetAllColors(w);
		redrawTiles = True;
	} else if (w->barrel.background != c->barrel.background ||
	    w->barrel.foreground != c->barrel.foreground ||
	    w->barrel.borderColor != c->barrel.borderColor ||
	    setColors) {
		SetAllColors(w);
		redrawTiles = True;
	}
	if (w->barrel.tiles != c->barrel.tiles ||
	    w->barrel.faces != c->barrel.faces ||
	    w->barrel.orient != c->barrel.orient ||
	    w->barrel.pairs != c->barrel.pairs ||
	    w->barrel.practice != c->barrel.practice ||
	    w->barrel.base != c->barrel.base) {
		ResetTiles(w);
		ResizeBarrel(w);
		redraw = True;
	} else if (w->barrel.offset.x != c->barrel.offset.x ||
		   w->barrel.offset.y != c->barrel.offset.y) {
		ResizeBarrel(w);
		redraw = True;
	}
	if (w->barrel.delay != c->barrel.delay) {
		w->barrel.numSlices = ((w->barrel.delay < MAXSLICES) ?
			w->barrel.delay + 1 : MAXSLICES);
	}
	if (w->barrel.menu != -1) {
		int menu = w->barrel.menu;

		w->barrel.menu = -1;
		switch (menu) {
		case 0:
			GetTiles(w);
			break;
		case 1:
			WriteTiles(w);
			break;
		case 3:
			ClearTiles(w);
			break;
		case 4:
			UndoTiles(w);
			break;
		case 5:
			RandomizeTiles(w);
			break;
		case 6:
			SolveTiles(w);
			break;
		case 7:
			OrientizeTiles(w);
			break;
		case 8:
			PairsTiles(w);
			break;
		case 9:
			PracticeTiles(w);
			break;
		case 10:
			SpeedTiles(w);
			break;
		case 11:
			SlowTiles(w);
			break;
		case 12:
			SoundTiles(w);
			break;
		default:
			break;
		}
	}
	if (redrawTiles && !redraw && XtIsRealized(renew) && renew->core.visible) {
		EraseFrame(c, 0);
		if (w->barrel.focus)
			DrawFrame(w, 0, True);
		DrawAllTiles(w);
	}
	return (redraw);
}

static void
QuitBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
#ifdef WINVER
	if (w->barrel.freeDC) {
		if (w->barrel.bufferTiles != NULL) {
			DeleteObject(w->barrel.bufferTiles);
		}
		DeleteDC(w->barrel.memDC);
		w->barrel.memDC = NULL;
	}
#else
	Display *display = XtDisplay(w);
	int sel;

	if (w->barrel.colormap != None) {
		XInstallColormap(display, w->barrel.oldColormap);
		XFreeColormap(display, w->barrel.colormap);
	}
	for (sel = 0; sel < 2; sel++)
		if (w->barrel.bufferTiles[sel] != None)
			XFreePixmap(display, w->barrel.bufferTiles[sel]);
	if (w->barrel.fontInfo) {
		XUnloadFont(display, w->barrel.fontInfo->fid);
		XFreeFont(display, w->barrel.fontInfo);
	}
	XtCloseDisplay(display);
#endif
	exit(0);
}

static void
DestroyBarrel(Widget old)
{
	BarrelWidget w = (BarrelWidget) old;
	int face;

	for (face = 0; face < MAXCOLORS; face++)
		XtReleaseGC(old, w->barrel.faceGC[face]);
	XtReleaseGC(old, w->barrel.tileGC);
	XtReleaseGC(old, w->barrel.borderGC);
	XtReleaseGC(old, w->barrel.tileBrighterGC);
	XtReleaseGC(old, w->barrel.tileDarkerGC);
	XtReleaseGC(old, w->barrel.frameGC);
	XtReleaseGC(old, w->barrel.inverseGC);
	XtRemoveCallbacks(old, XtNselectCallback, w->barrel.select);
}
#endif

static void
ResizeTiles(BarrelWidget w)
{
	int sel;

#ifdef WINVER
	if (w->barrel.memDC == NULL) {
		w->barrel.memDC = CreateCompatibleDC(w->core.hDC);
		if (w->barrel.memDC == NULL) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	for (sel = 0; sel < 2; sel++) {
		if (w->barrel.bufferTiles[sel] != NULL) {
			DeleteObject(w->barrel.bufferTiles[sel]);
				w->barrel.bufferTiles[sel] = NULL;
		}
		if (!(w->barrel.picture && *(w->barrel.picture))) {
		    if ((w->barrel.bufferTiles[sel] =
				CreateCompatibleBitmap(w->core.hDC,
				w->core.width, w->core.height)) == NULL) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		    }
		}
	}
#else
	Display *display = XtDisplay(w);
	Window window = XtWindow(w);
	XWindowAttributes xgwa;

	(void) XGetWindowAttributes(display, window, &xgwa);
	if (w->barrel.oldColormap == None) {
		w->barrel.mono = (xgwa.depth < 2 || w->barrel.mono);
		w->barrel.oldColormap = xgwa.colormap;
	}
	for (sel = 0; sel < 2; sel++) {
		if (w->barrel.bufferTiles[sel] != None) {
			XFreePixmap(display, w->barrel.bufferTiles[sel]);
			w->barrel.bufferTiles[sel] = None;
		}
		if ((w->barrel.bufferTiles[sel] = XCreatePixmap(display,
			window, w->core.width, w->core.height,
			xgwa.depth)) == None) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
#endif
	if (w->barrel.picture && *(w->barrel.picture)) {
#ifdef WINVER
		for (sel = 0; sel < 2; sel++) {
			w->barrel.bufferTiles[sel] =
				LoadBitmap(w->core.hInstance,
				w->barrel.picture);
		}
#else
		if (w->barrel.image != NULL) {
			destroyImage(&(w->barrel.image),
				&(w->barrel.graphicsFormat));
		}
		if (!getImage(display, window,
				xgwa.visual, w->barrel.oldColormap, xgwa.depth,
				&(w->barrel.image), w->barrel.picture,
				w->barrel.install, &(w->barrel.graphicsFormat),
				&(w->barrel.colormap))) {
			w->barrel.picture = NULL;
		} else if (w->barrel.image == NULL) {
			w->barrel.picture = NULL;
		}
#endif
	}
#ifndef WINVER
	if (!(w->barrel.picture && *(w->barrel.picture)) &&
	    !fixedColors(xgwa.visual, xgwa.depth, w->barrel.install) &&
	    w->barrel.colormap == None) {
		w->barrel.colormap = XCreateColormap(display, window,
			xgwa.visual, AllocNone);
	}
	SetAllColors(w);
	for (sel = 0; sel < 2; sel++) {
		FILLRECTANGLE(w, w->barrel.bufferTiles[sel],
			w->barrel.inverseGC,
			0, 0, w->core.width, w->core.height);
		if ((w->barrel.picture && *(w->barrel.picture))) {

			(void) XPutImage(display, w->barrel.bufferTiles[sel],
				w->barrel.inverseGC, w->barrel.image, 0, 0,
				0, 0,
				MIN(w->barrel.image->width, w->core.width),
				MIN(w->barrel.image->height, w->core.height));
		}
	}
#endif
	if (!(w->barrel.picture && *(w->barrel.picture))) {
		DrawAllBufferedTiles(w);
	} else if (!w->barrel.orient) {
		SetBarrel(w, BARREL_ORIENT);
	}
}

#ifndef WINVER
static
#endif
void
ResizeBarrel(BarrelWidget w)
{
#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->barrel.delta.x = 1;
	w->barrel.delta.y = 1;
	w->barrel.offset.x = MAX(((int) w->core.width - 2) /
		w->barrel.tiles, 0);
	w->barrel.offset.y = MAX((int) w->core.height /
		w->barrel.faces - 2, 0);
	w->barrel.faceSize.x = w->barrel.offset.x * w->barrel.tiles +
		w->barrel.delta.x + 2;
	w->barrel.faceSize.y = w->barrel.offset.y + w->barrel.delta.y + 2;
	w->barrel.puzzleSize.x = w->barrel.faceSize.x;
	w->barrel.puzzleSize.y = (w->barrel.faceSize.y - 2) *
		w->barrel.faces + 2;
	w->barrel.puzzleOffset.x = ((int) w->core.width -
		w->barrel.puzzleSize.x + 2) / 2;
	w->barrel.puzzleOffset.y = ((int) w->core.height -
		w->barrel.puzzleSize.y + 2) / 2;
	w->barrel.tileSize.x = MAX(w->barrel.offset.x - w->barrel.delta.x, 0);
	w->barrel.tileSize.y = MAX(w->barrel.offset.y - w->barrel.delta.y, 0);
}

#ifndef WINVER
static
#endif
void
SizeBarrel(BarrelWidget w)
{
	ResetTiles(w);
	ResizeBarrel(w);
}

#ifndef WINVER
static
#endif
void
InitializeBarrel(
#ifdef WINVER
BarrelWidget w, HBRUSH brush
#else
Widget request, Widget renew
#endif
)
{
#ifdef WINVER
	SetValuesBarrel(w);
	brush = CreateSolidBrush(w->barrel.inverseGC);
	SETBACK(w->core.hWnd, brush);
	(void) SRAND(time(NULL));
	w->barrel.bufferTiles[0] = NULL;
	w->barrel.bufferTiles[1] = NULL;
#else
	BarrelWidget w = (BarrelWidget) renew;

	(void) SRAND(getpid());
	w->barrel.bufferTiles[0] = None;
	w->barrel.bufferTiles[1] = None;
	w->barrel.colormap = None;
	w->barrel.oldColormap = None;
	w->barrel.fontInfo = NULL;
	w->barrel.tileGC = NULL;
	w->barrel.borderGC = NULL;
	w->barrel.tileBrighterGC = NULL;
	w->barrel.tileDarkerGC = NULL;
	w->barrel.frameGC = NULL;
	w->barrel.inverseGC = NULL;
#endif
	w->barrel.focus = False;
	loadFont(w);
	w->barrel.tiles = MAXTILES;
	w->barrel.faces = MAXFACES;
	w->barrel.tileOfPosition = NULL;
	CheckTiles(w);
	InitMoves();
	ResetTiles(w);
	w->barrel.numSlices = ((w->barrel.delay < MAXSLICES) ?
		w->barrel.delay + 1 : MAXSLICES);
	w->barrel.cheat = False;
	SizeBarrel(w);
}

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

	if (!w->core.visible)
		return;
#endif
	ResizeTiles(w);
	EraseFrame(w, 0);
	DrawFrame(w, 0, w->barrel.focus);
	DrawAllTiles(w);
}

#ifndef WINVER
static
#endif
void
HideBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	SetBarrel(w, BARREL_HIDE);
}

#ifndef WINVER
static
#endif
void
SelectBarrel(BarrelWidget w
#ifdef WINVER
, const int x, const int y, const int shift
#else
, XEvent * event, char **args, int nArgs
#endif
)
{
	int i, j, pos;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int shift = (int) (event->xkey.state & (ShiftMask | LockMask));
#endif

	w->barrel.motion = False;
	w->barrel.motionDiff = 0;
	w->barrel.shiftDiff = 0;
	pos = SelectTiles(w, x, y, &i, &j);
	if (-w->barrel.tileFaces != pos) {
		w->barrel.currentTile = i;
		w->barrel.currentFace = j;
		w->barrel.currentRef = pos;
		if (shift != 0 || w->barrel.practice || !CheckSolved(w)) {
			pos = w->barrel.currentTile + w->barrel.currentFace * w->barrel.tiles;
			DrawTile(w, pos, (w->barrel.tileOfPosition[i + j * w->barrel.tiles] < 0),
				 False, TRUE, 0, 0);
		}
	} else
		w->barrel.currentRef = -w->barrel.tileFaces;
}

#ifndef WINVER
static
#endif
void
MotionBarrel(BarrelWidget w
#ifdef WINVER
, const int x, const int y, const int shift
#else
, XEvent * event, char **args, int nArgs
#endif
)
{
	int i, j, pos, diff;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int shift = (int) (event->xkey.state & (ShiftMask | LockMask));
#endif

	if (w->barrel.currentRef == -w->barrel.tileFaces)
		return;
	if (shift == 0 && !w->barrel.practice && CheckSolved(w)) {
		MoveNoTiles(w);
		w->barrel.currentRef = -w->barrel.tileFaces;
		return;
	}
	pos = SelectTiles(w, x, y, &i, &j);
	if (w->barrel.currentTile != i) {
		int k =  w->barrel.currentTile +
			w->barrel.currentFace * w->barrel.tiles;

		DrawTile(w, k, True, True, TRUE, 0, 0);
		DrawTile(w, k, (w->barrel.tileOfPosition[k] <= 0),
			(w->barrel.tileOfPosition[k] <= 0), FALSE, 0, 0);
		DiffRecord(w, w->barrel.motionDiff, j, FALSE);
		DiffRecord(w, w->barrel.shiftDiff, j, TRUE);
		if (shift != 0 && CheckSolved(w)) {
			SetBarrel(w, BARREL_SOLVED);
		}
		w->barrel.currentTile = i;
		w->barrel.currentFace = j;
		w->barrel.currentRef = pos;
		w->barrel.motionDiff = 0;
		w->barrel.shiftDiff = 0;
	}
	if (-w->barrel.tileFaces != pos) {
		if (j != w->barrel.currentFace) {
			if (shift != 0 && (w->barrel.currentTile == 0 ||
					w->barrel.currentTile == w->barrel.tiles - 1)) {
				w->barrel.currentRef = -w->barrel.tileFaces;
				return;
			}
			diff = (w->barrel.currentFace - j +
				w->barrel.faces) % w->barrel.faces;
			DiffMove(w, diff, j, shift);
			if (shift != 0 && CheckSolved(w)) {
				SetBarrel(w, BARREL_SOLVED);
			}
			w->barrel.motion = True;
			if (shift == 0)
				w->barrel.motionDiff = (diff +
					w->barrel.motionDiff) %
					w->barrel.faces;
			else
				w->barrel.shiftDiff = (diff +
					w->barrel.shiftDiff) % w->barrel.faces;
		}
		w->barrel.currentTile = i;
		w->barrel.currentFace = j;
		w->barrel.currentRef = pos;
		if (shift != 0 || w->barrel.practice || !CheckSolved(w)) {
			pos = w->barrel.currentTile + w->barrel.currentFace * w->barrel.tiles;
			DrawTile(w, pos, (w->barrel.tileOfPosition[i + j * w->barrel.tiles] < 0),
				 False, TRUE, 0, 0);
		}
	} else
		w->barrel.currentRef = -w->barrel.tileFaces;
}

#ifndef WINVER
static
#endif
void
ReleaseBarrel(BarrelWidget w
#ifdef WINVER
, const int x, const int y, const int shift
#else
, XEvent * event, char **args, int nArgs
#endif
)
{
	int i, j, pos, diff;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int shift = (int) (event->xkey.state & (ShiftMask | LockMask));
#endif

	if (w->barrel.currentRef == -w->barrel.tileFaces)
		return;
	pos = w->barrel.currentTile + w->barrel.currentFace * w->barrel.tiles;
	DrawTile(w, pos, True, True, TRUE, 0, 0);
	if (w->barrel.tileOfPosition[pos] > 0) {
		DrawTile(w, pos, False, False, FALSE, 0, 0);
	}
	if (shift == 0 && !w->barrel.practice && CheckSolved(w)) {
		MoveNoTiles(w);
		w->barrel.currentRef = -w->barrel.tileFaces;
		return;
	}
	pos = SelectTiles(w, x, y, &i, &j);
	if (-w->barrel.tileFaces != pos) {
		if (j == w->barrel.currentFace && !w->barrel.motion) {
			if ((w->barrel.spacePosition[0] % w->barrel.tiles) == 0)
				diff = w->barrel.tiles - 1;
			else
				diff = -w->barrel.tiles + 1;
			(void) SelectSlideTiles(w, diff);
			if (CheckSolved(w)) {
				SetBarrel(w, BARREL_SOLVED);
			}
		} else {
			if (shift == 0 && (w->barrel.currentTile == 0 ||
					w->barrel.currentTile == w->barrel.tiles - 1)) {
				w->barrel.currentRef = -w->barrel.tileFaces;
				return;
			}
			diff = (w->barrel.currentFace - j + w->barrel.faces) %
				w->barrel.faces;
			DiffMove(w, diff, j, shift);
			if (shift == 0)
				w->barrel.motionDiff = (diff +
					w->barrel.motionDiff) %
					w->barrel.faces;
			else
				w->barrel.shiftDiff = (diff +
					w->barrel.shiftDiff) % w->barrel.faces;
			DiffRecord(w, w->barrel.motionDiff, j, FALSE);
			DiffRecord(w, w->barrel.shiftDiff, j, TRUE);
			if (shift == 0 && CheckSolved(w)) {
				SetBarrel(w, BARREL_SOLVED);
			}
		}
	}
	w->barrel.currentRef = -w->barrel.tileFaces;
}

#ifndef WINVER
static
#endif
void
PracticeBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	PracticeTiles(w);
}

#ifndef WINVER
static void
PracticeBarrelMaybe(BarrelWidget w
, XEvent * event, char **args, int nArgs
)
{
	if (!w->barrel.started)
		PracticeTiles(w);
#ifdef HAVE_MOTIF
	else {
		SetBarrel(w, BARREL_PRACTICE_QUERY);
	}
#endif
}

static void
PracticeBarrel2(BarrelWidget w
, XEvent * event, char **args, int nArgs
)
{
#ifdef HAVE_MOTIF
	if (!w->barrel.started)
#endif
		PracticeTiles(w);
}
#endif

#ifndef WINVER
static
#endif
void
RandomizeBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	RandomizeTiles(w);
}

#ifndef WINVER
static void
RandomizeBarrelMaybe(BarrelWidget w
, XEvent * event, char **args, int nArgs
)
{
	if (!w->barrel.started)
		RandomizeTiles(w);
#ifdef HAVE_MOTIF
	else {
		SetBarrel(w, BARREL_RANDOMIZE_QUERY);
	}
#endif
}

static void
RandomizeBarrel2(BarrelWidget w
, XEvent * event, char **args, int nArgs
)
{
#ifdef HAVE_MOTIF
	if (!w->barrel.started)
#endif
		RandomizeTiles(w);
}
#endif

#ifndef WINVER
static
#endif
void
GetBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	GetTiles(w);
}

#ifndef WINVER
static
#endif
void
WriteBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	WriteTiles(w);
}

#ifndef WINVER
static
#endif
void
ClearBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	ClearTiles(w);
}

#ifndef WINVER
static
#endif
void
UndoBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	UndoTiles(w);
}

#ifndef WINVER
static
#endif
void
SolveBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	SolveTiles(w);
}

#ifndef WINVER
static
#endif
void
OrientizeBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	OrientizeTiles(w);
}

#ifndef WINVER
static
#endif
void
PairsBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	PairsTiles(w);
}

#ifndef WINVER
static
#endif
void
SpeedBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	SpeedTiles(w);
}

#ifndef WINVER
static
#endif
void
SlowBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	SlowTiles(w);
}

#ifndef WINVER
static
#endif
void
SoundBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	SoundTiles(w);
}

#ifndef WINVER
static
#endif
void
EnterBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	w->barrel.focus = True;
	DrawFrame(w, 0, w->barrel.focus);
#ifndef WINVER
	if (w->barrel.colormap != None)
		XInstallColormap(XtDisplay(w), w->barrel.colormap);
#endif
}

#ifndef WINVER
static
#endif
void
LeaveBarrel(BarrelWidget w
#ifndef WINVER
, XEvent * event, char **args, int nArgs
#endif
)
{
	w->barrel.focus = False;
	DrawFrame(w, 0, w->barrel.focus);
#ifndef WINVER
	if (w->barrel.colormap != None)
		XInstallColormap(XtDisplay(w), w->barrel.oldColormap);
#endif
}

#ifndef WINVER
static void
MoveBarrelTop(BarrelWidget w, XEvent * event, char **args, int nArgs)
{
	MoveBarrelInput(w, event->xbutton.x, TOP,
		(int) (event->xkey.state & (ShiftMask | LockMask)));
}

static void
MoveBarrelBottom(BarrelWidget w, XEvent * event, char **args, int nArgs)
{
	MoveBarrelInput(w, event->xbutton.x, BOTTOM,
		(int) (event->xkey.state & (ShiftMask | LockMask)));
}

static void
MoveBarrelLeft(BarrelWidget w, XEvent * event, char **args, int nArgs)
{
	MoveBarrelInput(w, event->xbutton.x, LEFT,
		(int) (event->xkey.state & (ShiftMask | LockMask)));
}

static void
MoveBarrelRight(BarrelWidget w, XEvent * event, char **args, int nArgs)
{
	MoveBarrelInput(w, event->xbutton.x, RIGHT,
		(int) (event->xkey.state & (ShiftMask | LockMask)));
}
#endif
