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

/* Methods file for Barrel */

#include "file.h"
#include "rngs.h"
#include "sound.h"
#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
#define hasValue(x) (*(x))
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "C:/ProgramData/wpuzzles/wbarrel.ini"
#endif
#define SECTION "setup"

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

static char faceColorChar[MAX_COLORS] =
{'G', 'R', 'Y', 'O', 'B', 'b'};
#else
#define hasValue(x) (x && *(x))
#include "picture.h"
#include "pixmaps/grain_lr.xbm"
#include "pixmaps/grain_tb.xbm"

#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 Boolean setValuesPuzzle(Widget current, Widget request, Widget renew);
static void quitPuzzle(BarrelWidget w, XEvent *event, char **args, int nArgs);
static void destroyPuzzle(Widget old);
static void resizePuzzle(BarrelWidget w);
static void initializePuzzle(Widget request, Widget renew);
static void exposePuzzle(Widget renew, XEvent *event, Region region);
static void hidePuzzle(BarrelWidget w, XEvent *event, char **args, int nArgs);
static void selectPuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void motionPuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void releasePuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzleWithQuery(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzleWithDoubleClick(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzleWithQuery(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzleWithDoubleClick(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void getPuzzle(BarrelWidget w, XEvent *event, char **args, int nArgs);
static void writePuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void undoPuzzle(BarrelWidget w, XEvent *event, char **args, int nArgs);
static void redoPuzzle(BarrelWidget w, XEvent *event, char **args, int nArgs);
static void clearPuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void solvePuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void orientizePuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void pairsPuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void speedUpPuzzle(BarrelWidget w,XEvent *event, char **args, int nArgs);
static void slowDownPuzzle(BarrelWidget w, XEvent *event, char **args, int nArgs);
static void toggleSoundPuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void enterPuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void leavePuzzle(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTop(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(BarrelWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(BarrelWidget 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>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\
 <Btn2Down>: PracticeMaybe()\n\
 <Btn2Down>(2+): Practice2()\n\
 <Btn3Down>: RandomizeMaybe()\n\
 <Btn3Down>(2+): Randomize2()\n\
 <Btn4Down>: MoveTop()\n\
 <Btn5Down>: MoveBottom()\n\
 <KeyPress>g: Get()\n\
 <KeyPress>w: Write()\n\
 <KeyPress>u: Undo()\n\
 <KeyPress>r: Redo()\n\
 <KeyPress>c: Clear()\n\
 <KeyPress>z: Randomize()\n\
 <KeyPress>s: Solve()\n\
 <KeyPress>p: Practice()\n\
 <KeyPress>o: Orientize()\n\
 <KeyPress>i: Pairs()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsList[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "MoveTop", (XtActionProc) movePuzzleTop},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleBottom},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Motion", (XtActionProc) motionPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "PracticeMaybe", (XtActionProc) practicePuzzleWithQuery},
	{(char *) "Practice2", (XtActionProc) practicePuzzleWithDoubleClick},
	{(char *) "RandomizeMaybe", (XtActionProc) randomizePuzzleWithQuery},
	{(char *) "Randomize2", (XtActionProc) randomizePuzzleWithDoubleClick},
	{(char *) "Get", (XtActionProc) getPuzzle},
	{(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 *) "Orientize", (XtActionProc) orientizePuzzle},
	{(char *) "Pairs", (XtActionProc) pairsPuzzle},
	{(char *) "Speed", (XtActionProc) speedUpPuzzle},
	{(char *) "Slow", (XtActionProc) slowDownPuzzle},
	{(char *) "Sound", (XtActionProc) toggleSoundPuzzle},
	{(char *) "Enter", (XtActionProc) enterPuzzle},
	{(char *) "Leave", (XtActionProc) leavePuzzle}
};

static XtResource resources[] =
{
	{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"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{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) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(BarrelWidget, barrel.frameColor),
	 XtRString, (caddr_t) "wheat4" /*XtDefaultForeground*/},
	{XtNtileColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(BarrelWidget, barrel.tileColor),
	 XtRString, (caddr_t) "gray75" /*XtDefaultForeground*/},
	{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"},
	{XtNtileBorder, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(BarrelWidget, barrel.borderColor),
	 XtRString, (caddr_t) "gray50" /*XtDefaultBackground*/},
	{XtNstippleFrame, XtCStippleFrame, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.stippleFrame),
	 XtRString, (caddr_t) "TRUE"},
	{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"}, /* DEFAULT_DELAY */
	{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"},
	{XtNtiles, XtCTiles, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.tiles),
	 XtRString, (caddr_t) "6"}, /* DEFAULT_TILES */
	{XtNfaces, XtCFaces, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.faces),
	 XtRString, (caddr_t) "5"}, /* DEFAULT_FACES */
	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.orient),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULT_ORIENT */
	{XtNpairs, XtCPairs, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.pairs),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_PAIRS */
	{XtNpractice, XtCPractice, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.practice),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_PRACTICE */
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.base),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_BASE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(BarrelWidget, barrel.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(BarrelWidget, barrel.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{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"},
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(BarrelWidget, barrel.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{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) 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 barrelWidgetClass = (WidgetClass) & barrelClassRec;

void
setPuzzle(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.font && (w->barrel.fontInfo =
			XLoadQueryFont(display, w->barrel.font)) == NULL) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 512,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->barrel.font, altfontname);
#else
		(void) sprintf(buf,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->barrel.font, altfontname);
#endif
		DISPLAY_WARNING(buf);
		if ((w->barrel.fontInfo = XLoadQueryFont(display,
				altfontname)) == NULL) {
#ifdef HAVE_SNPRINTF
			(void) snprintf(buf, 512,
				"Cannot open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
#else
			(void) sprintf(buf,
				"Cannot open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
#endif
			DISPLAY_WARNING(buf);
		}
	}
	if (w->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

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

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

	if (w->barrel.tiles < MIN_TILES) {
		intCat(&buf1,
			"Number of tiles in X direction out of bounds, use at least ",
			MIN_TILES);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_TILES);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->barrel.tiles = DEFAULT_TILES;
	}
	if (w->barrel.faces < MIN_FACES || w->barrel.faces > MAX_FACES) {
		intCat(&buf1,
			"Number of faces out of bounds, use ",
			MIN_FACES);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAX_FACES);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_FACES);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->barrel.faces = DEFAULT_FACES;
	}
	if (w->barrel.base < MIN_BASE || w->barrel.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->barrel.base = DEFAULT_BASE;
	}
}

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
toColumn(BarrelWidget w, int pos)
{
	return (pos % w->barrel.tiles);
}

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


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

static int
cartesianY(BarrelWidget w, int pos)
{
	return toRow(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.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 & 1) == 0) && ((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 (!hasValue(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 < PLUNGERS_NUMBER; face++) {
		if (tile == inc++)
			return (face * 2 + 1) * w->barrel.tiles - 1;
	}
	for (face = 0; face < w->barrel.faces - PLUNGERS_NUMBER; 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
void
printPuzzle(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 < PLUNGERS_NUMBER; 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] = (char) (digit + '0');
		if (buf[position] > '9') {	/* ASCII */
			buf[position] = (char) (buf[position] + (a - '9' - 1));
		} else if (buf[position] < '0') {	/* EBCDIC */
			buf[position] = (char) (buf[position] + (a - '9' - 1));
			if (buf[position] > i)
				buf[position] = (char) (buf[position] + (j - i - 1));
			if (buf[position] > r)
				buf[position] = (char) (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);
		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[1],
			dx, dy,
			w->barrel.tileSize.x, w->barrel.tileSize.y);
		return;
	}
#ifdef WINVER
	w->core.hOldBitmap = (HBITMAP) SelectObject(w->core.memDC,
		w->barrel.bufferTiles[pressedOffset]);
	BitBlt(w->core.hDC,
		dx, dy,
		w->barrel.tileSize.x, w->barrel.tileSize.y,
		w->core.memDC,
		sx, sy,
		SRCCOPY);
	(void) SelectObject(w->core.memDC, w->core.hOldBitmap);
#else
	XSetGraphicsExposures(XtDisplay(w), w->barrel.tileGC[1], False);
	XCopyArea(XtDisplay(w),
		w->barrel.bufferTiles[pressedOffset],
		XtWindow(w),
		w->barrel.tileGC[1],
		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[1];
	borderGC = w->barrel.borderGC;
	if ((w->barrel.mono && pressedOffset) || tile <= 0) {
		ballGC = borderGC;
	} else {
		int face = ((tile - 1) / (w->barrel.tiles - 2)) % MAX_COLORS;

		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.tileGC[1],
			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.tileGC[2], w->barrel.tileGC[0],
		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 +
			PLUNGERS_NUMBER)) {
		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)) %
							MAX_COLORS];
#else
						w->barrel.faceName[((tile - 1) /
							(w->barrel.tiles -2)) %
							MAX_COLORS][0];
#endif
					buf[last + 1] = '\0';
					t *= w->barrel.base;
				} else {
					buf[0] =
#ifdef WINVER
						w->barrel.faceChar[((tile - 1) /
							(w->barrel.tiles - 2)) %
							MAX_COLORS];
#else
						w->barrel.faceName[((tile - 1) /
							(w->barrel.tiles - 2)) %
							MAX_COLORS][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 numOfTiles, int dir, int fast)
{
	int inc, aTile, plunger;
	int fillBeginPos[PLUNGERS_NUMBER];
	int fillBeginI = 0, gapI = 0, moveI = 0;
	int dx, dy, pos, posNext;
	int ix = 0, iy = 0;
	int numTiles = numOfTiles;
	Pixmap dr = 0;

	for (plunger = 0; plunger < PLUNGERS_NUMBER; 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;
	FLUSH(w);
	initTimer(w->barrel.oldTime);
	for (inc = 0; inc < moveI + gapI; inc += gapI) {
	  if (inc > moveI)
	    inc = moveI;
	  for (plunger = 0; plunger < PLUNGERS_NUMBER; 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) {
		if (dir == RIGHT) {
		  FILLRECTANGLE(w, dr,
		    w->barrel.inverseGC,
		    ix - gapI, iy, gapI, w->barrel.tileSize.y);
		} else if (dir == LEFT) {
		  FILLRECTANGLE(w, dr,
		    w->barrel.inverseGC,
		    ix + w->barrel.tileSize.x, iy,
		    gapI, w->barrel.tileSize.y);
		}
	      } else {
		if (dir == RIGHT) {
		  FILLRECTANGLE(w, dr,
		    w->barrel.inverseGC,
		    fillBeginI, iy,
		    ix - fillBeginI, w->barrel.tileSize.y);
		} else if (dir == LEFT) {
		  FILLRECTANGLE(w, dr,
		    w->barrel.inverseGC,
		    ix + w->barrel.tileSize.x, iy,
		    fillBeginI - ix, w->barrel.tileSize.y);
		}
	      }
	    }
	    FLUSH(w);
	    useTimer(&(w->barrel.oldTime), w->barrel.delay / fast);
	  }
	}
	for (plunger = 0; plunger < PLUNGERS_NUMBER; 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]] = -PLUNGERS_NUMBER +
		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
drawFrameLR(const BarrelWidget w, Pixmap dr)
{
	GC gc = w->barrel.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->barrel.stippleFrame && w->barrel.grainLR != (Pixmap) NULL) {
		XSetStipple(display, w->barrel.frameGC, w->barrel.grainLR);
		XSetFillStyle(display, w->barrel.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->barrel.frameGC, FillSolid);
#endif
	/* Top */
	tempList[0].x = 0;
	tempList[0].y = 0;
	tempList[1].x = w->core.width - 1;
	tempList[1].y = 0;
	tempList[2].x = w->core.width - w->barrel.puzzleOffset.x - 1;
	tempList[2].y = w->barrel.puzzleOffset.y;
	tempList[3].x = w->barrel.puzzleOffset.x;
	tempList[3].y = w->barrel.puzzleOffset.y;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
	/* Bottom */
	tempList[0].x = w->core.width - 1;
	tempList[0].y = w->core.height - 1;
	tempList[1].x = 0;
	tempList[1].y = w->core.height - 1;
	tempList[2].x = w->barrel.puzzleOffset.x;
	tempList[2].y = w->core.height - w->barrel.puzzleOffset.y - 1;
	tempList[3].x = w->core.width - w->barrel.puzzleOffset.x - 1;
	tempList[3].y = w->core.height - w->barrel.puzzleOffset.y - 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
}

static void
drawFrameTB(const BarrelWidget w, Pixmap dr)
{
	GC gc = w->barrel.frameGC;
	Point tempList[5];
#ifndef WINVER
	Display *display = XtDisplay(w);
	if (w->barrel.stippleFrame && w->barrel.grainTB != (Pixmap) NULL) {
		XSetStipple(display, w->barrel.frameGC, w->barrel.grainTB);
		XSetFillStyle(display, w->barrel.frameGC, FillOpaqueStippled);
	} else
		XSetFillStyle(display, w->barrel.frameGC, FillSolid);
#endif
	/* Left */
	tempList[0].x = w->barrel.puzzleOffset.x;
	tempList[0].y = w->core.height - w->barrel.puzzleOffset.y - 1;
	tempList[1].x = 0;
	tempList[1].y = w->core.height - 1;
	tempList[2].x = 0;
	tempList[2].y = 0;
	tempList[3].x = w->barrel.puzzleOffset.x;
	tempList[3].y = w->barrel.puzzleOffset.y;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
	/* Right */
	tempList[0].x = w->core.width - w->barrel.puzzleOffset.x - 1;
	tempList[0].y = w->barrel.puzzleOffset.y;
	tempList[1].x = w->core.width - 1;
	tempList[1].y = 0;
	tempList[2].x = w->core.width - 1;
	tempList[2].y = w->core.height - 1;
	tempList[3].x = w->core.width - w->barrel.puzzleOffset.x - 1;
	tempList[3].y = w->core.height - w->barrel.puzzleOffset.y - 1;
	tempList[4].x = tempList[0].x;
	tempList[4].y = tempList[0].y;
	POLYGON(w, dr, gc, gc,
		tempList, 4, True, True);
}

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

	offsetX = w->barrel.puzzleOffset.x;
	offsetY = w->barrel.puzzleOffset.y;
	drawFrameTB(w, dr);
	drawFrameLR(w, dr);
	/*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
animateRotate(BarrelWidget w, int start, int finish, int direction, int fast)
{
#ifdef WINVER
	HDC memDC;
	HBITMAP hBitmap, hOldBitmap;
#else
	Pixmap bufferBox;
	XWindowAttributes xgwa;
#endif
	int size = finish - start + 1;
	int moveI, gapJ, moveJ, inc, i, offset = 0;

	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 + 1;
	if (gapJ == 0)
		gapJ++;

#ifdef WINVER
	memDC = CreateCompatibleDC(w->core.hDC);
	hBitmap = CreateCompatibleBitmap(w->core.hDC, size * moveI + 1,
		w->barrel.puzzleSize.y);
	hOldBitmap = SelectObject(memDC, hBitmap);
	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);
	(void) SelectObject(memDC, hOldBitmap);
#else
	(void) XGetWindowAttributes(XtDisplay(w), XtWindow(w), &xgwa);

	if ((bufferBox = XCreatePixmap(XtDisplay(w), XtWindow(w),
			size * moveI + 1,
			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
	FLUSH(w);
	initTimer(w->barrel.oldTime);
	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;
			offset = 3;
		}
#ifdef WINVER
		hOldBitmap = SelectObject(memDC, hBitmap);
		BitBlt(w->core.hDC,
			start * moveI + w->barrel.puzzleOffset.x + 1,
			w->barrel.puzzleOffset.y + offset,
			size * moveI,
			w->barrel.puzzleSize.y - i - 1,
			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 + offset,
			size * moveI,
			i - w->barrel.delta.y - 1,
			memDC,
			0, 0,
			SRCCOPY);
		(void) SelectObject(memDC, hOldBitmap);
#else
		XCopyArea(XtDisplay(w), bufferBox, XtWindow(w),
			w->barrel.frameGC,
			0, i,
			size * moveI,
			w->barrel.puzzleSize.y - i - 1,
			start * moveI + w->barrel.puzzleOffset.x + 1,
			w->barrel.puzzleOffset.y + offset);
		XCopyArea(XtDisplay(w), bufferBox, XtWindow(w),
			w->barrel.frameGC,
			0, 0,
			size * moveI,
			i - w->barrel.delta.y - 1,
			start * moveI + w->barrel.puzzleOffset.x + 1,
			w->barrel.puzzleSize.y - i + w->barrel.puzzleOffset.y -
			w->barrel.delta.y - 1 + offset);
		if (inc == 0)
			drawFrame(w, 0, w->barrel.focus);
#endif
		FLUSH(w);
		useTimer(&(w->barrel.oldTime), w->barrel.delay / fast);
	}
#ifdef WINVER
	DeleteObject(hBitmap);
	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
moveNoTiles(BarrelWidget w)
{
	setPuzzle(w, ACTION_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 < PLUNGERS_NUMBER; 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] = -PLUNGERS_NUMBER + plunger;
			drawTile(w, pos, True, True, FALSE, 0, 0);
		}
	}
	w->barrel.currentRef = -w->barrel.tileFaces;
#ifdef USE_SOUND
	if (w->barrel.sound) {
		playSound(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 < PLUNGERS_NUMBER; plunger++) {
				if (w->barrel.spacePosition[plunger] == pos) {
					if (!animate)
						drawTile(w, newPos, True, True,
							FALSE, 0, 0);
					break;
				}
			}
			if (!animate && plunger == PLUNGERS_NUMBER)
				drawTile(w, newPos, False, False,
					FALSE, 0, 0);
			r = newR;
		}
	}
	if (start == 0) {
		for (plunger = 0; plunger < PLUNGERS_NUMBER; plunger++) {
			i = (direction == TOP) ? w->barrel.faces - 1 : 1;
			w->barrel.spacePosition[plunger] =
				(w->barrel.spacePosition[plunger] + i *
					w->barrel.tiles) % w->barrel.tileFaces;
		}
	}
	/* Needed in case a window is on top. */
	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);
	}
#ifdef USE_SOUND
	if (w->barrel.sound) {
		playSound(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 (toColumn(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 (toColumn(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);
	setPuzzle(w, ACTION_CONTROL);
}

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

	switch (direction) {
	case TOP:
		if (w->barrel.faces >= MAX_FACES)
			return;
		reason = ACTION_INCY;
		break;
	case RIGHT:
		reason = ACTION_INCX;
		break;
	case BOTTOM:
		if (w->barrel.faces <= MIN_FACES)
			return;
		reason = ACTION_DECY;
		break;
	case LEFT:
		if (w->barrel.tiles <= MIN_TILES)
			return;
		reason = ACTION_DECX;
		break;
	default:
		{
			char *buf;

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

Boolean
movePuzzle(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) {
			setMove(&undo, direction, tile, 1);
			flushMoves(w, &redo, FALSE);
		}
		return True;
	} else if (moveTilesDir(w, direction, tile, False, fast)) {
		if (!motion) {
			setPuzzle(w, ACTION_MOVED);
			setMove(&undo, direction, tile, 0);
			flushMoves(w, &redo, FALSE);
		}
		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) movePuzzle(w, BOTTOM, w->barrel.currentTile,
				shift, True, NORMAL);
	else
		for (k = 0; k < diff; k++)
			(void) movePuzzle(w, TOP, w->barrel.currentTile,
				shift, True, NORMAL);
}

static void
movePosition(BarrelWidget w,
		const int direction, const int tile, const int shift)
{
	if (shift != 0 && (direction == TOP || direction == BOTTOM)) {
		setMove(&undo, direction, tile, 1);
	} else {
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, direction, tile, 0);
	}
	flushMoves(w, &redo, FALSE);
}

static void
diffPosition(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) movePosition(w, BOTTOM,
				w->barrel.currentTile, shift);
	else
		for (k = 0; k < diff; k++)
			(void) movePosition(w, TOP,
				w->barrel.currentTile, shift);
}

void
movePuzzleDelay(BarrelWidget w, const int direction, const int tile,
		const Boolean all)
{
	if (direction == RIGHT) {
		setMove(&undo, direction, 0, False);
	} else if (direction == LEFT) {
		setMove(&undo, direction,
			w->barrel.tiles - 1, False);
	} else {
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, direction, tile, all);
	}
	flushMoves(w, &redo, FALSE);
	(void) moveTilesDir(w, direction, tile, all, NORMAL);
	Sleep((unsigned int) w->barrel.delay);
}

static void
slidePuzzle(BarrelWidget w, int direction)
{
	if (checkSolved(w) && !w->barrel.practice) {
		moveNoTiles(w);
		return;
	}
	if (!movePuzzle(w, direction, 0, FALSE, False, NORMAL)) {
		setPuzzle(w, ACTION_BLOCKED);
		return;
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
rotatePuzzle(BarrelWidget w, int direction, int tile)
{
	if (checkSolved(w) && !w->barrel.practice) {
		moveNoTiles(w);
		return;
	}
	(void) movePuzzle(w, direction, tile, FALSE, False, NORMAL);
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

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

	if (shift != 0 && (direction == TOP || direction == BOTTOM)) {
		moveShift(w, direction, NORMAL);
		setMove(&undo, direction, 0, 1);
		flushMoves(w, &redo, FALSE);
	} else if (checkSolved(w) && !w->barrel.practice) {
		moveNoTiles(w);
		return;
	} else if (direction == LEFT || direction == RIGHT) {
		slidePuzzle(w, direction);
		if (checkSolved(w)) {
			setPuzzle(w, ACTION_SOLVED);
		}
	} else {
		if (!positionTile(w, x, &r))
			return;
		if (r == 0 || r == w->barrel.tiles - 1) {
			setPuzzle(w, ACTION_BLOCKED);
			return;
		}
		rotatePuzzle(w, direction, r);
		if (checkSolved(w)) {
			setPuzzle(w, ACTION_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(BUMPSOUND);
			}
#endif
		} else {
			moveTiles(w, w->barrel.spacePosition[0] % w->barrel.tiles - w->barrel.tiles + 1,
				NORMAL);
		}
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, RIGHT, w->barrel.currentTile, 0);
		flushMoves(w, &redo, FALSE);
	} else if (pos > 0) {
		if (w->barrel.delay > 0) {
			animateSlide(w, pos, LEFT, NORMAL);
#ifdef USE_SOUND
			if (w->barrel.sound) {
				playSound(BUMPSOUND);
			}
#endif
		} else {
			moveTiles(w, w->barrel.spacePosition[0] % w->barrel.tiles + w->barrel.tiles - 1,
				NORMAL);
		}
		setPuzzle(w, ACTION_MOVED);
		setMove(&undo, LEFT, w->barrel.currentTile, 0);
		flushMoves(w, &redo, FALSE);
	}
}

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) * (size_t) w->barrel.tileFaces))) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	if (startPosition)
		free(startPosition);
	if (!(startPosition = (int *)
			malloc(sizeof (int) * (size_t) 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 < PLUNGERS_NUMBER; i++) {
		w->barrel.spacePosition[i] = 2 * i * w->barrel.tiles;
		w->barrel.tileOfPosition[2 * i * w->barrel.tiles] =
			-PLUNGERS_NUMBER + i;
		inc++;
		w->barrel.tileOfPosition[(2 * i + 1) * w->barrel.tiles - 1] =
			inc;
	}
	inc++;
	for (i = 0; i < w->barrel.faces - PLUNGERS_NUMBER; i++) {
		w->barrel.tileOfPosition[(2 * i + 1) * w->barrel.tiles] =
			inc++;
		w->barrel.tileOfPosition[(2 * i + 2) * w->barrel.tiles - 1] =
			inc++;
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->barrel.currentRef = -w->barrel.tileFaces;
	w->barrel.started = False;
}

static void
readFile(BarrelWidget w, FILE *fp, char *name)
{
	int c, i, tiles, faces, pairs, orient, practice, moves;
	char *buf1 = NULL, *buf2 = NULL;

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &tiles) != 1) {
		(void) printf("corrupt record, expecting an integer for tiles\n");
		return;
	}
	if (tiles >= MIN_TILES) {
		for (i = w->barrel.tiles; i < tiles; i++) {
			setPuzzle(w, ACTION_INCX);
		}
		for (i = w->barrel.tiles; i > tiles; i--) {
			setPuzzle(w, ACTION_DECX);
		}
	} else {
		stringCat(&buf1, name, " corrupted: tiles ");
		intCat(&buf2, buf1, tiles);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_TILES);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &faces) != 1) {
		(void) printf("corrupt record, expecting an integer for faces\n");
		return;
	}
	if (faces >= MIN_FACES && faces <= MAX_FACES) {
		for (i = w->barrel.faces; i < faces; i++) {
			setPuzzle(w, ACTION_INCY);
		}
		for (i = w->barrel.faces; i > faces; i--) {
			setPuzzle(w, ACTION_DECY);
		}
	} else {
		stringCat(&buf1, name, " corrupted: faces ");
		intCat(&buf2, buf1, faces);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_FACES);
		free(buf1);
		stringCat(&buf1, buf2, " and ");
		free(buf2);
		intCat(&buf2, buf1, MAX_FACES);
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &pairs) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for pairs\n");
		return;
	}
	if (w->barrel.pairs != (Boolean) pairs) {
		setPuzzle(w, ACTION_PAIRS);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &orient) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for orient\n");
		return;
	}
	if (w->barrel.orient != (Boolean) orient) {
		setPuzzle(w, ACTION_ORIENTIZE);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &practice) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for practice\n");
		return;
	}
	if (w->barrel.practice != (Boolean) practice) {
		setPuzzle(w, ACTION_PRACTICE);
	}
#ifdef WINVER
	resetTiles(w);
#endif
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &moves) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for moves\n");
		return;
	}
	if (!scanStartPosition(fp, w))
		return;
	setPuzzle(w, ACTION_RESTORE);
	setStartPosition(w);
	if (!scanMoves(fp, w, moves))
		return;
	(void) printf(
		"%s: tiles %d, faces %d, pairs %s, orient %s, practice %s, moves %d\n",
		name, tiles, faces,
		BOOL_STRING(pairs), BOOL_STRING(orient), BOOL_STRING(practice),
		moves);
}

static void
getTiles(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, "r")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "r")) == NULL) {
			stringCat(&buf1, "Cannot read ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot read ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	readFile(w, fp, name);
	(void) fclose(fp);
	free(lname);
	free(fname);
	w->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, "Cannot write to ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	(void) fprintf(fp, "tiles%c %d\n", SYMBOL, w->barrel.tiles);
	(void) fprintf(fp, "faces%c %d\n", SYMBOL, w->barrel.faces);
	(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(&undo));
	printStartPosition(fp, w);
	printMoves(fp, &undo);
	(void) fclose(fp);
	(void) printf("Saved to %s\n", name);
	free(lname);
	free(fname);
}

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

		getMove(&undo, &direction, &tile, &shift);
		setMove(&redo, 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)) {
			setPuzzle(w, ACTION_UNDO);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		} else {
			char *buf1, *buf2;

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

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

		getMove(&redo, &direction, &tile, &shift);
		setMove(&undo, direction, tile, shift);
		if (shift != 0 && (direction == TOP || direction == BOTTOM)) {
			moveShift(w, direction, DOUBLE);
		} else if (moveTilesDir(w, direction, tile, False, DOUBLE)) {
			setPuzzle(w, ACTION_REDO);
			if (checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		} else {
			char *buf1, *buf2;

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

static void
clearTiles(BarrelWidget w)
{
	if (w->barrel.currentRef != -w->barrel.tileFaces)
		return;
	resetTiles(w);
	drawAllTiles(w);
	setPuzzle(w, ACTION_RESET);
}

static void
practiceTiles(BarrelWidget w)
{
	setPuzzle(w, ACTION_PRACTICE);
}

static void
randomizeTiles(BarrelWidget w)
{
	if (w->barrel.currentRef != -w->barrel.tileFaces)
		return;
	if (w->barrel.practice)
		practiceTiles(w);
	setPuzzle(w, ACTION_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 RDEBUG
		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) movePuzzle(w, randomDirection,
						w->barrel.tiles - 1, FALSE,
						False, INSTANT);
				} else {
					(void) movePuzzle(w, randomDirection,
						NRAND(w->barrel.tiles - 2) + 1,
						FALSE, False, INSTANT);
				}
#ifdef RDEBUG
				sleep(1);
#endif
			} else {
				big++;
			}
		}
		flushMoves(w, &undo, TRUE);
		flushMoves(w, &redo, FALSE);
		setPuzzle(w, ACTION_RANDOMIZE);
	}
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_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 DEBUG
	solveSomeTiles(w);
#else
	if (!w->barrel.orient) {
		solveSomeTiles(w);
	} else {
		setPuzzle(w, ACTION_SOLVE_MESSAGE);
	}
#endif
}

static void
orientizeTiles(BarrelWidget w)
{
	setPuzzle(w, ACTION_ORIENTIZE);
}

static void
pairsTiles(BarrelWidget w)
{
	setPuzzle(w, ACTION_PAIRS);
}

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

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

static void
soundTiles(BarrelWidget w)
{
	w->barrel.sound = !w->barrel.sound;
	setPuzzle(w, ACTION_SOUND);
}

#define BRIGHT_FACTOR 0.8
#define DARK_FACTOR 0.75

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

	if (temp < i)
		temp = i;
	return MIN(temp / BRIGHT_FACTOR, MAX_INTENSITY);
}

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

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

	w->barrel.tiles = (int) GetPrivateProfileInt(SECTION,
		"tiles", DEFAULT_TILES, INIFILE);
	w->barrel.faces = (int) GetPrivateProfileInt(SECTION,
		"faces", DEFAULT_FACES, INIFILE);
	w->barrel.orient = (BOOL) GetPrivateProfileInt(SECTION,
		"orient", DEFAULT_ORIENT, INIFILE);
	w->barrel.pairs = (BOOL) GetPrivateProfileInt(SECTION,
		"pairs", DEFAULT_PAIRS, INIFILE);
	w->barrel.practice = (BOOL) GetPrivateProfileInt(SECTION,
		"practice", DEFAULT_PRACTICE, INIFILE);
	w->barrel.base = (int) GetPrivateProfileInt(SECTION,
		"base", DEFAULT_BASE, INIFILE);
	w->barrel.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->barrel.reverse = (BOOL) GetPrivateProfileInt(SECTION,
		"reverseVideo", DEFAULT_REVERSE, INIFILE);
	/* Tan (wheat4) */
	(void) GetPrivateProfileString(SECTION,
		"frameColor", "139 126 102", 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[1] = 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.tileGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->barrel.tileGC[2] = 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 < MAX_COLORS; face++) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "faceColor%d", face);
#else
		(void) sprintf(buf, "faceColor%d", face);
#endif
		(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);
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "faceChar%d", face);
#else
		(void) sprintf(buf, "faceChar%d", face);
#endif
		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) strncpy(w->barrel.picture, szBuf, 80);
	w->barrel.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", DEFAULT_DELAY, INIFILE);
	w->barrel.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", 0, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"bumpSound", BUMPSOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->barrel.bumpSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"moveSound", MOVESOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->barrel.moveSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->barrel.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->barrel.scoreFile, szBuf, 80);
}

void
destroyPuzzle(BarrelWidget w, HBRUSH brush)
{
	if (w->core.memDC) {
		DeleteObject(w->barrel.bufferTiles);
		DeleteDC(w->core.memDC);
		w->core.memDC = NULL;
	}
	deleteMoves(&undo);
	deleteMoves(&redo);
	(void) DeleteObject(brush);
	PostQuitMessage(0);
}

#else
#define MAX_INTENSITY 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 - BRIGHT_FACTOR) * MAX_INTENSITY);

	color.pixel = pixel;
	VOID 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 / BRIGHT_FACTOR, MAX_INTENSITY);
	color.green = (unsigned short) MIN(color.green / BRIGHT_FACTOR, MAX_INTENSITY);
	color.blue = (unsigned short) MIN(color.blue / BRIGHT_FACTOR, MAX_INTENSITY);
	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;
	VOID XQueryColor(XtDisplay(w), colormap, &color);
	color.red = (unsigned short) (color.red * DARK_FACTOR);
	color.green = (unsigned short) (color.green * DARK_FACTOR);
	color.blue = (unsigned short) (color.blue * DARK_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)
{
	Display *display = XtDisplay(w);
	Window window = XtWindow(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.foreground;
	}
	if (w->barrel.frameGC)
		XtReleaseGC((Widget) w, w->barrel.frameGC);
	w->barrel.frameGC = XtGetGC((Widget) w, valueMask, &values);
	w->barrel.grainLR = XCreateBitmapFromData(display, window,
		(char *) grain_lr_bits, grain_lr_width, grain_lr_height);
	w->barrel.grainTB = XCreateBitmapFromData(display, window,
		(char *) grain_tb_bits, grain_tb_width, grain_tb_height);
	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[1])
		XtReleaseGC((Widget) w, w->barrel.tileGC[1]);
	w->barrel.tileGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->barrel.mono) {
		values.foreground = darker(w, w->barrel.tileColor);
	}
	if (w->barrel.tileGC[0])
		XtReleaseGC((Widget) w, w->barrel.tileGC[0]);
	w->barrel.tileGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->barrel.mono) {
		values.foreground = brighter(w, w->barrel.tileColor);
	}
	if (w->barrel.tileGC[2])
		XtReleaseGC((Widget) w, w->barrel.tileGC[2]);
	w->barrel.tileGC[2] = 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 < MAX_COLORS; face++)
		getColor(w, face);
	if (w->barrel.fontInfo) {
		XSetFont(XtDisplay(w), w->barrel.tileGC[1],
			w->barrel.fontInfo->fid);
	}
}

static Boolean
setValuesPuzzle(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 < MAX_FACES; 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);
		resizePuzzle(w);
		redraw = True;
	} else if (w->barrel.offset.x != c->barrel.offset.x ||
			w->barrel.offset.y != c->barrel.offset.y) {
		resizePuzzle(w);
		redraw = True;
	}
	if (w->barrel.delay != c->barrel.delay) {
		w->barrel.numSlices = ((w->barrel.delay < MAX_SLICES) ?
			w->barrel.delay + 1 : MAX_SLICES);
	}
	if (w->barrel.menu != ACTION_IGNORE) {
		int menu = w->barrel.menu;

		w->barrel.menu = ACTION_IGNORE;
		switch (menu) {
		case ACTION_GET:
			getTiles(w);
			break;
		case ACTION_WRITE:
			writeTiles(w);
			break;
		case ACTION_UNDO:
			undoTiles(w);
			break;
		case ACTION_REDO:
			redoTiles(w);
			break;
		case ACTION_CLEAR:
			clearTiles(w);
			break;
		case ACTION_PRACTICE:
			practiceTiles(w);
			break;
		case ACTION_RANDOMIZE:
			randomizeTiles(w);
			break;
		case ACTION_SOLVE:
			solveTiles(w);
			break;
		case ACTION_ORIENTIZE:
			orientizeTiles(w);
			break;
		case ACTION_PAIRS:
			pairsTiles(w);
			break;
		case ACTION_SPEED:
			speedTiles(w);
			break;
		case ACTION_SLOW:
			slowTiles(w);
			break;
		case ACTION_SOUND:
			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
destroyPuzzle(Widget old)
{
	BarrelWidget w = (BarrelWidget) old;
	Display *display = XtDisplay(w);
	int i;

#if defined( USE_SOUND ) && defined( USE_ESOUND )
	(void) shutdown_sound();
#endif
	for (i = 0; i < MAX_COLORS; i++)
		XtReleaseGC(old, w->barrel.faceGC[i]);
	for (i = 0; i < FG_SHADES; i++)
		w->barrel.tileGC[i] = NULL;
	XtReleaseGC(old, w->barrel.borderGC);
	XtReleaseGC(old, w->barrel.frameGC);
	XtReleaseGC(old, w->barrel.inverseGC);
	XtRemoveCallbacks(old, XtNselectCallback, w->barrel.select);
	if (w->barrel.colormap != None) {
		XInstallColormap(display, w->barrel.oldColormap);
		XFreeColormap(display, w->barrel.colormap);
	}
	for (i = 0; i < 2; i++)
		if (w->barrel.bufferTiles[i] != None)
			XFreePixmap(display, w->barrel.bufferTiles[i]);
	if (w->barrel.fontInfo) {
		XUnloadFont(display, w->barrel.fontInfo->fid);
		XFreeFont(display, w->barrel.fontInfo);
	}
	deleteMoves(&undo);
	deleteMoves(&redo);
}

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

static void
resizeTiles(BarrelWidget w)
{
	int sel;

#ifdef WINVER
	if (w->core.memDC == NULL) {
		w->core.memDC = CreateCompatibleDC(w->core.hDC);
		if (w->core.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 (!hasValue(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 (hasValue(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 (!hasValue(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 (hasValue(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 (!hasValue(w->barrel.picture)) {
		drawAllBufferedTiles(w);
	} else if (!w->barrel.orient) {
		setPuzzle(w, ACTION_ORIENTIZE);
	}
}

#ifndef WINVER
static
#endif
void
resizePuzzle(BarrelWidget w)
{
	int frameThickness;
#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
	frameThickness = MIN(16, MIN(w->core.width, w->core.height) / 16);
	w->barrel.delta.x = 1;
	w->barrel.delta.y = 1;
	w->barrel.offset.x = MAX(((int) w->core.width - 2 * frameThickness - 2) /
		w->barrel.tiles, 0);
	w->barrel.offset.y = MAX(((int) w->core.height - 2 * frameThickness) /
		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
sizePuzzle(BarrelWidget w)
{
	resetTiles(w);
	resizePuzzle(w);
}

#ifndef WINVER
static
#endif
void
initializePuzzle(
#ifdef WINVER
BarrelWidget w, HBRUSH brush
#else
Widget request, Widget renew
#endif
)
{
#ifdef WINVER
	setValuesPuzzle(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;
	int i;

	(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;
	for (i = 0; i < FG_SHADES; i++)
		w->barrel.tileGC[i] = NULL;
	w->barrel.borderGC = NULL;
	w->barrel.frameGC = NULL;
	w->barrel.inverseGC = NULL;
	w->barrel.image = NULL;
#endif
	w->barrel.focus = False;
	loadFont(w);
	w->barrel.tiles = MAX_TILES;
	w->barrel.faces = MAX_FACES;
	w->barrel.tileOfPosition = NULL;
	checkTiles(w);
	newMoves(&undo);
	newMoves(&redo);
	resetTiles(w);
	w->barrel.numSlices = ((w->barrel.delay < MAX_SLICES) ?
		w->barrel.delay + 1 : MAX_SLICES);
	w->barrel.cheat = False;
	sizePuzzle(w);
#ifdef USE_SOUND
#ifdef USE_NAS
	dsp = XtDisplay(w);
#endif
#ifdef USE_ESOUND
	(void) init_sound();
#endif
#endif
}

void
exposePuzzle(
#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
hidePuzzle(BarrelWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	setPuzzle(w, ACTION_HIDE);
}

#ifndef WINVER
static
#endif
void
selectPuzzle(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
motionPuzzle(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 (shift == 0 && (w->barrel.currentTile == 0 ||
			w->barrel.currentTile == w->barrel.tiles - 1) &&
			j != w->barrel.currentFace) {
		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);
		setPuzzle(w, ACTION_BLOCKED);
		w->barrel.currentRef = -w->barrel.tileFaces;
		return;
	}
	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);
		diffPosition(w, w->barrel.motionDiff, j, FALSE);
		diffPosition(w, w->barrel.shiftDiff, j, TRUE);
		if (shift != 0 && checkSolved(w)) {
			setPuzzle(w, ACTION_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)) {
				setPuzzle(w, ACTION_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
releasePuzzle(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)) {
				setPuzzle(w, ACTION_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;
			diffPosition(w, w->barrel.motionDiff, j, FALSE);
			diffPosition(w, w->barrel.shiftDiff, j, TRUE);
			if (shift == 0 && checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
	w->barrel.currentRef = -w->barrel.tileFaces;
}

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

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

#ifndef WINVER
static void
randomizePuzzleWithQuery(BarrelWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->barrel.started)
		randomizeTiles(w);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	else {
		setPuzzle(w, ACTION_RANDOMIZE_QUERY);
	}
#endif
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#ifndef WINVER
static
#endif
void
enterPuzzle(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
leavePuzzle(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
movePuzzleTop(BarrelWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, TOP,
		(int) (event->xkey.state & (ShiftMask | LockMask)));
}

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

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

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