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

/* Methods file for Mlink */

#include "file.h"
#include "rngs.h"
#include "sound.h"
#include "MlinkP.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 "wmlink.ini"
#endif
#define SECTION "setup"

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

static char faceColorChar[MAX_FACES] =
{'W', 'Y', 'G', 'R', 'B', 'M', 'C', 'O'};
#else
#include "picture.h"

#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(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void destroyPuzzle(Widget old);
static void resizePuzzle(MlinkWidget w);
static void sizePuzzle(MlinkWidget w);
static void initializePuzzle(Widget request, Widget renew);
static void exposePuzzle(Widget renew, XEvent *event, Region region);
static void hidePuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void selectPuzzle(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void motionPuzzle(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void releasePuzzle(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzleWithQuery(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void practicePuzzleWithDoubleClick(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzleWithQuery(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void randomizePuzzleWithDoubleClick(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void getPuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void writePuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void undoPuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void redoPuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void clearPuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void randomizePuzzle(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void solvePuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void practicePuzzle(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void orientizePuzzle(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void middlePuzzle(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void speedUpPuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void slowDownPuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void toggleSoundPuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void enterPuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void leavePuzzle(MlinkWidget w, XEvent *event, char **args, int nArgs);
static void movePuzzleTop(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(MlinkWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(MlinkWidget 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>: PracticeWithQuery()\n\
 <Btn2Down>(2+): PracticeWithDoubleClick()\n\
 <Btn3Down>: RandomizeWithQuery()\n\
 <Btn3Down>(2+): RandomizeWithDoubleClick()\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>m: Middle()\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 *) "PracticeWithQuery", (XtActionProc) practicePuzzleWithQuery},
	{(char *) "PracticeWithDoubleClick", (XtActionProc) practicePuzzleWithDoubleClick},
	{(char *) "RandomizeWithQuery", (XtActionProc) randomizePuzzleWithQuery},
	{(char *) "RandomizeWithDoubleClick", (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 *) "Middle", (XtActionProc) middlePuzzle},
	{(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(MlinkWidget, core.width),
	 XtRString, (caddr_t) "512"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(MlinkWidget, core.height),
	 XtRString, (caddr_t) "512"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(MlinkWidget, mlink.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(MlinkWidget, mlink.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(MlinkWidget, mlink.frameColor),
	 XtRString, (caddr_t) "cyan" /*XtDefaultForeground*/},
	{XtNtileColor, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(MlinkWidget, mlink.tileColor),
	 XtRString, (caddr_t) "black" /*XtDefaultForeground*/},
	{XtNfaceColor0, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.faceName[0]),
	 XtRString, (caddr_t) "white"},
	{XtNfaceColor1, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.faceName[1]),
	 XtRString, (caddr_t) "yellow"},
	{XtNfaceColor2, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.faceName[2]),
	 XtRString, (caddr_t) "green"},
	{XtNfaceColor3, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.faceName[3]),
	 XtRString, (caddr_t) "red"},
	{XtNfaceColor4, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.faceName[4]),
	 XtRString, (caddr_t) "blue"},
	{XtNfaceColor5, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.faceName[5]),
	 XtRString, (caddr_t) "magenta"},
	{XtNfaceColor6, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.faceName[6]),
	 XtRString, (caddr_t) "cyan"},
	{XtNfaceColor7, XtCLabel, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.faceName[7]),
	 XtRString, (caddr_t) "orange"},
	{XtNtileBorder, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(MlinkWidget, mlink.borderColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultBackground*/},
	{XtNinstall, XtCInstall, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.install),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpicture, XtCPicture, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.picture),
	 XtRString, (caddr_t) PICTURE},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(MlinkWidget, mlink.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNbumpSound, XtCBumpSound, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.bumpSound),
	 XtRString, (caddr_t) BUMPSOUND},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNtiles, XtCTiles, XtRInt, sizeof (int),
	 XtOffset(MlinkWidget, mlink.tiles),
	 XtRString, (caddr_t) "4"}, /* DEFAULT_TILES */
	{XtNfaces, XtCFaces, XtRInt, sizeof (int),
	 XtOffset(MlinkWidget, mlink.faces),
	 XtRString, (caddr_t) "4"}, /* DEFAULT_FACES */
	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.orient),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULT_ORIENT */
	{XtNmiddle, XtCMiddle, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.middle),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_MIDDLE */
	{XtNpractice, XtCPractice, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.practice),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_PRACTICE */
	{XtNbase, XtCBase, XtRInt, sizeof (int),
	 XtOffset(MlinkWidget, mlink.base),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_BASE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(MlinkWidget, mlink.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(MlinkWidget, mlink.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(MlinkWidget, mlink.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(MlinkWidget, mlink.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(MlinkWidget, mlink.select),
	 XtRCallback, (caddr_t) NULL}
};

MlinkClassRec mlinkClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Mlink",	/* class name */
		sizeof (MlinkRec),	/* 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 mlinkWidgetClass = (WidgetClass) & mlinkClassRec;

void
setPuzzle(MlinkWidget w, int reason)
{
	mlinkCallbackStruct cb;

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

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

	if (w->mlink.fontInfo) {
		XUnloadFont(display, w->mlink.fontInfo->fid);
		XFreeFont(display, w->mlink.fontInfo);
	}
	if (w->mlink.font && (w->mlink.fontInfo =
			XLoadQueryFont(display, w->mlink.font)) == NULL) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 512,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->mlink.font, altfontname);
#else
		(void) sprintf(buf,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->mlink.font, altfontname);
#endif
		DISPLAY_WARNING(buf);
		if ((w->mlink.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->mlink.fontInfo) {
		w->mlink.digitOffset.x = XTextWidth(w->mlink.fontInfo, "8", 1)
			/ 2;
		w->mlink.digitOffset.y = w->mlink.fontInfo->max_bounds.ascent
			/ 2;
	} else
#endif
	{
		w->mlink.digitOffset.x = 3;
		w->mlink.digitOffset.y = 4;
	}
}

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

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

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

	if (w->mlink.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->mlink.tiles = DEFAULT_TILES;
	}
	if (w->mlink.faces < MIN_FACES || w->mlink.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->mlink.faces = DEFAULT_FACES;
	}
	if (w->mlink.base < MIN_BASE || w->mlink.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->mlink.base = DEFAULT_BASE;
	}
}

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

static int
toColumn(MlinkWidget w, int pos)
{
	return (pos % w->mlink.tiles);
}

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

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

static int
cartesianY(MlinkWidget w, int pos)
{
	return toRow(w, pos) * (w->mlink.offset.y + 1) + w->mlink.delta.y +
		w->mlink.puzzleOffset.y;
}

static int
tileNFromSpace(MlinkWidget w, int n, int direction)
{
	int pos = w->mlink.spacePosition;

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

static int
solvedPosition(MlinkWidget w, int tile) {
	return (tile + 1) % w->mlink.tileFaces;
}

Boolean
checkSolved(const MlinkWidget w)
{
	int i, j, firstTile, lastTile, midTile;

	if (w->mlink.tiles < 2)
		return True;
	if (w->mlink.orient) {
		firstTile = w->mlink.tileOfPosition[0];
		if (firstTile != 0 && (firstTile - 1) % w->mlink.tiles == 0) {
			for (i = 1; i < w->mlink.tileFaces; i++) {
				midTile = w->mlink.tileOfPosition[i];
				if (midTile && (midTile - firstTile + w->mlink.tileFaces) %
						w->mlink.tileFaces != i)
					return False;
			}
		} else
			return False;
	} else {
		for (i = 0; i < w->mlink.faces; i++) {
			firstTile = w->mlink.tileOfPosition[i * w->mlink.tiles];
			if (firstTile == 0)
				firstTile = w->mlink.tileOfPosition[i * w->mlink.tiles + 1];
			lastTile = w->mlink.tileOfPosition[(i + 1) * w->mlink.tiles - 1];
			if (lastTile == 0)
				lastTile = w->mlink.tileOfPosition[(i + 1) * w->mlink.tiles - 2];
			if (firstTile % w->mlink.tiles != 1 ||
					(lastTile % w->mlink.tiles != 0 &&
					lastTile != w->mlink.tileFaces - 1) ||
					(lastTile - 1) / w->mlink.tiles !=
					(firstTile - 1) / w->mlink.tiles)
				return False;
			for (j = 1; j < w->mlink.tiles - 1; j++) {
				midTile = w->mlink.tileOfPosition[i * w->mlink.tiles + j];
				if ((midTile - 1) / w->mlink.tiles !=
						(firstTile - 1) /
						w->mlink.tiles || midTile == 0)
					return False;
			}
		}
	}
	return True;
}

static int
int2String(MlinkWidget 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(MlinkWidget 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 - 1);
	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 THICKNESS 7
static void
drawLink(MlinkWidget w, Pixmap dr, GC gc, int pos, int offsetX, int offsetY)
{
	int dx, dy, sizeX, sizeY, thick;
	int tile = solvedPosition(w, pos);
	int angle1, angle2;

	sizeX = w->mlink.offset.x * 3 / 2 - 6;
	sizeY = w->mlink.offset.y * 3 / 4 - 6;
	dx = cartesianX(w, pos) - sizeX / 2 + offsetX + 2;
	dy = cartesianY(w, pos) + w->mlink.tileSize.y / 2 - sizeY / 2 -
		1 + offsetY;
	thick = MIN(sizeY, sizeX) / 3;
	thick = MIN(thick, THICKNESS);
	if (thick > 0) {
		if (tile % w->mlink.tiles == 0 || tile == w->mlink.tileFaces - 1) {

#ifdef WINVER
			angle1 = 87, angle2 = -174;
#else
			angle1 = 89, angle2 = -178;
#endif
			DRAWARC(w, dr, gc, thick, dx, dy,
				sizeX, sizeY, angle1, angle2);
		} else if (tile % w->mlink.tiles == 1) {
#ifdef WINVER
			angle1 = 93, angle2 = 174;
#else
			angle1 = 90, angle2 = 180;
#endif
			DRAWARC(w, dr, gc, thick, dx + w->mlink.tileSize.x - 5, dy,
				sizeX, sizeY, angle1, angle2);
		} else {
#ifdef WINVER
			angle1 = 87, angle2 = -120;
#else
			angle1 = 89, angle2 = -124;
#endif
			DRAWARC(w, dr, gc, thick, dx, dy,
				sizeX, sizeY, angle1, angle2);
#ifdef WINVER
			angle1 = -87, angle2 = 25;
#else
			angle1 = -89, angle2 = 29;
#endif
			DRAWARC(w, dr, gc, thick, dx, dy,
				sizeX, sizeY, angle1, angle2);
			dx += w->mlink.tileSize.x - 5;
#ifdef WINVER
			angle1 = 93, angle2 = 25;
#else
			angle1 = 90, angle2 = 30;
#endif
			DRAWARC(w, dr, gc, thick, dx, dy,
				sizeX, sizeY, angle1, angle2);
#ifdef WINVER
			angle1 = -93, angle2 = -120;
#else
			angle1 = -90, angle2 = -125;
#endif
			DRAWARC(w, dr, gc, thick, dx, dy,
				sizeX, sizeY, angle1, angle2);
		}
	}
}

#if 0
#define THICKNESS 8
static void
drawLink(MlinkWidget w, Pixmap dr, GC gc, int pos, int offsetX, int offsetY)
{
	int dx, dy, sizeX, sizeY, i;
	int tile = solvedPosition(w, pos);

	for (i = 0; i < THICKNESS; i++) {
		sizeX = w->mlink.offset.x * 3 / 2 - i;
		sizeY = w->mlink.offset.y * 3 / 4 - i;
		dx = cartesianX(w, pos) - sizeX / 2 + offsetX;
		dy = cartesianY(w, pos) + w->mlink.tileSize.y / 2 - sizeY / 2 +
			offsetY;
		if (tile % w->mlink.tiles == 0 ||
				tile == w->mlink.tileFaces - 1) {
			DRAWARC(w, dr, gc, 1, gc, dx, dy,
				sizeX, sizeY, 89 * MULT, -179 * MULT);
		} else if (tile % w->mlink.tiles == 1) {
			DRAWARC(w, dr, gc, 1, gc,
				dx + w->mlink.tileSize.x - 1, dy,
				sizeX, sizeY, 90 * MULT, 180 * MULT);
		} else {
			DRAWARC(w, dr, gc, 1, gc, dx, dy,
				sizeX, sizeY, 89 * MULT, -32 * MULT);
			DRAWARC(w, dr, gc, 1, gc, dx, dy,
				sizeX, sizeY, -90 * MULT, 128 * MULT);
			dx += w->mlink.tileSize.x - 1;
			DRAWARC(w, dr, gc, 1, gc, dx, dy,
				sizeX, sizeY, 90 * MULT, 128 * MULT);
			DRAWARC(w, dr, gc, 1, gc, dx, dy,
				sizeX, sizeY, -90 * MULT, -33 * MULT);
		}
	}
}
#endif

static void
drawShadow(MlinkWidget 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(MlinkWidget w, int pos, Boolean blank, Boolean erase,
		int pressedOffset, int offsetX, int offsetY)
{
	int dx, dy, sx, sy;
	int tile = (w->mlink.tileOfPosition[pos] + w->mlink.tileFaces - 1) %
		w->mlink.tileFaces;
	Pixmap dr = 0;

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

static void
drawBufferedTile(MlinkWidget w, int pos, int pressedOffset)
{
	Pixmap *dr;
	GC linkGC, tileGC, borderGC;
	int dx, dy, tile = solvedPosition(w, pos);
	int i = 0, digitOffsetX = 0, t = tile;
	char buf[66];

	dr = &(w->mlink.bufferTiles[pressedOffset]);
	tileGC = w->mlink.tileGC[1];
	borderGC = w->mlink.borderGC;
	if (w->mlink.mono && pressedOffset) {
		linkGC = borderGC;
	} else {
		int face = ((tile - 1) / w->mlink.tiles + 1) % w->mlink.faces;

		linkGC = w->mlink.faceGC[face];
#ifndef WINVER
		if (!w->mlink.mono) {
			if (w->mlink.faceColor[face] == w->mlink.tileColor) {
				linkGC = (pressedOffset) ? tileGC : borderGC;
			}
		}
#endif
	}
	dx = cartesianX(w, pos) + pressedOffset;
	dy = cartesianY(w, pos) + pressedOffset;
	if (pressedOffset != 0) {
		drawShadow(w, *dr, w->mlink.tileGC[2],
			dx - pressedOffset, dy - pressedOffset,
			w->mlink.tileSize.x - 1, w->mlink.tileSize.y - 1);
	}
	fill3DRect(w, *dr, tileGC,
		w->mlink.tileGC[2], w->mlink.tileGC[0],
		dx, dy, w->mlink.tileSize.x, w->mlink.tileSize.y,
		pressedOffset == 0);
	buf[0] = '\0';
	if (w->mlink.orient || w->mlink.mono) {
		if (w->mlink.mono) {
			if (w->mlink.orient) {
				int last;

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

static void
drawAllBufferedTiles(const MlinkWidget w)
{
	int k, l;

	for (k = 0; k < w->mlink.tileFaces; k++)
		for (l = 0; l < 2; l++)
			drawBufferedTile(w, k, l);
}

void
drawAllTiles(const MlinkWidget w)
{
	int k;

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

static void
animateSlide(MlinkWidget w, int numOfTiles, int dir, int fast, Boolean logMoves)
{
	int inc, aTile;
	int fillBeginPos, fillBeginI = 0, gapI = 0, moveI = 0;
	int dx, dy, pos, posNext;
	int ix = 0, iy = 0;
	int numTiles = numOfTiles;
	Pixmap dr = 0;

	fillBeginPos = tileNFromSpace(w, numTiles, dir);
	fillBeginI = cartesianX(w, fillBeginPos);
	dy = cartesianY(w, fillBeginPos);
	gapI = w->mlink.tileSize.x * fast / w->mlink.numSlices;
	moveI = w->mlink.tileSize.x + w->mlink.delta.x;
	if (gapI == 0)
		gapI++;
	if (numTiles < 0)
		numTiles = -numTiles;
	FLUSH(w);
	initTimer(w->mlink.oldTime);
	for (inc = 0; inc < moveI + gapI; inc += gapI) {
	  if (inc > moveI)
	    inc = moveI;
	  for (aTile = 0; aTile < numTiles; aTile++) {
	    posNext = tileNFromSpace(w, aTile + 1, dir);
	    if (logMoves && inc == 0) {
	      setPuzzle(w, ACTION_MOVED);
	      setMove(&undo, dir, posNext, 0);
	      flushMoves(w, &redo, FALSE);
	    }
	    /* 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->mlink.inverseGC,
		  ix - gapI, iy, gapI, w->mlink.tileSize.y);
	      } else if (dir == LEFT) {
		FILLRECTANGLE(w, dr,
		  w->mlink.inverseGC,
		  ix + w->mlink.tileSize.x, iy,
		  gapI, w->mlink.tileSize.y);
	      }
	    } else {
	      if (dir == RIGHT) {
		FILLRECTANGLE(w, dr,
		  w->mlink.inverseGC,
		  fillBeginI, iy,
		  ix - fillBeginI, w->mlink.tileSize.y);
	      } else if (dir == LEFT) {
		FILLRECTANGLE(w, dr,
		  w->mlink.inverseGC,
		  ix + w->mlink.tileSize.x, iy,
		  fillBeginI - ix, w->mlink.tileSize.y);
	      }
	    }
	  }
	  FLUSH(w);
	  useTimer(&(w->mlink.oldTime), w->mlink.delay / fast);
	}
	pos = w->mlink.spacePosition;
	for (aTile = 0; aTile < numTiles; aTile++) {
	  posNext = tileNFromSpace(w, aTile + 1, dir);
	  w->mlink.tileOfPosition[pos] =
	    w->mlink.tileOfPosition[posNext];
	  pos = posNext;
	}
	w->mlink.spacePosition = fillBeginPos;
	w->mlink.tileOfPosition[fillBeginPos] = 0;
}

static void
drawAllTilesForColumns(MlinkWidget w, int start, int finish)
{
	int i, j, k;

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

static void
animateRotate(MlinkWidget 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->mlink.tileSize.x + w->mlink.delta.x;
	gapJ = w->mlink.tileSize.y * fast / w->mlink.numSlices;
	moveJ = w->mlink.tileSize.y + w->mlink.delta.y + 1;
	if (gapJ == 0)
		gapJ++;

#ifdef WINVER
	memDC = CreateCompatibleDC(w->core.hDC);
	hBitmap = CreateCompatibleBitmap(w->core.hDC, size * moveI + 1,
			w->mlink.puzzleSize.y);
	hOldBitmap = SelectObject(memDC, hBitmap);
	BitBlt(memDC, 0, 0,
		size * moveI,
		w->mlink.puzzleSize.y,
		w->core.hDC,
		start * moveI + w->mlink.puzzleOffset.x + 1,
		w->mlink.puzzleOffset.y,
		SRCCOPY);
	(void) SelectObject(memDC, hOldBitmap);
#else
	(void) XGetWindowAttributes(XtDisplay(w), XtWindow(w), &xgwa);
	if ((bufferBox = XCreatePixmap(XtDisplay(w), XtWindow(w),
			size * moveI,
			w->mlink.puzzleSize.y,
			xgwa.depth)) == None) {
		DISPLAY_ERROR("Not enough memory, exiting.");
	}
	XCopyArea(XtDisplay(w), XtWindow(w), bufferBox,
		w->mlink.frameGC,
		start * moveI + w->mlink.puzzleOffset.x + 1,
		w->mlink.puzzleOffset.y,
		size * moveI,
		w->mlink.puzzleSize.y,
		0, 0);
	XSetGraphicsExposures(XtDisplay(w), w->mlink.frameGC, False);
#endif
	FLUSH(w);
	initTimer(w->mlink.oldTime);
	for (inc = 0; inc < moveJ + gapJ; inc += gapJ) {
		if (inc > moveJ)
			inc = moveJ;
		if (direction == TOP) {
			i = inc;
		} else {
			i = w->mlink.puzzleSize.y - inc + 1;
			offset = 3;
		}
#ifdef WINVER
		hOldBitmap = SelectObject(memDC, hBitmap);
		BitBlt(w->core.hDC,
			start * moveI + w->mlink.puzzleOffset.x + 1,
			w->mlink.puzzleOffset.y + offset,
			size * moveI,
			w->mlink.puzzleSize.y - i - 1,
			memDC,
			0, i,
			SRCCOPY);
		BitBlt(w->core.hDC,
			start * moveI + w->mlink.puzzleOffset.x + 1,
			w->mlink.puzzleSize.y - i + w->mlink.puzzleOffset.y -
			w->mlink.delta.y - 1 + offset,
			size * moveI,
			i - w->mlink.delta.y - 1,
			memDC,
			0, 0,
			SRCCOPY);
		(void) SelectObject(memDC, hOldBitmap);
#else
		XCopyArea(XtDisplay(w), bufferBox, XtWindow(w),
			w->mlink.frameGC,
			0, i,
			size * moveI,
			w->mlink.puzzleSize.y - i - 1,
			start * moveI + w->mlink.puzzleOffset.x + 1,
			w->mlink.puzzleOffset.y + offset);
		XCopyArea(XtDisplay(w), bufferBox, XtWindow(w),
			w->mlink.frameGC,
			0, 0,
			size * moveI,
			i - w->mlink.delta.y - 1,
			start * moveI + w->mlink.puzzleOffset.x + 1,
			w->mlink.puzzleSize.y - i + w->mlink.puzzleOffset.y -
			w->mlink.delta.y - 1 + offset);
#endif
		FLUSH(w);
		useTimer(&(w->mlink.oldTime), w->mlink.delay / fast);
	}
#ifdef WINVER
	DeleteObject(hBitmap);
	DeleteDC(memDC);
#else
	XFreePixmap(XtDisplay(w), bufferBox);
#endif
}

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

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


	sumX = w->mlink.tiles * w->mlink.offset.x + 1;
	sumY = w->mlink.offset.y + 1;
	for (k = 0; k < w->mlink.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
eraseFrame(const MlinkWidget w, Pixmap dr)
{
	FILLRECTANGLE(w, dr, w->mlink.inverseGC,
		0, 0, w->core.width, w->core.height);
}

static void
moveNoTiles(MlinkWidget w)
{
	setPuzzle(w, ACTION_IGNORE);
}

static void
moveTiles(MlinkWidget w, int from, int fast)
{
	int i =	w->mlink.currentTile = toColumn(w, from);
	int j = w->mlink.currentFace = toRow(w, from);
	int l = i + w->mlink.tiles * j - w->mlink.spacePosition;

	w->mlink.currentRef = from;
	if (l / w->mlink.tiles == 0 &&
			j == toRow(w, w->mlink.spacePosition)) {
		if (fast != INSTANT && w->mlink.delay > 0) {

			if (l < 0) {
				animateSlide(w, -l, RIGHT, fast, False);
			} else { /* (l > 0) */
				animateSlide(w, l, LEFT, fast, False);
			}
		} else {
			int pos, posNext, aTile, dir;

			dir = (l < 0) ? RIGHT : LEFT;
			if (l < 0)
				l = -l;
			pos = w->mlink.spacePosition;
			for (aTile = 0; aTile < l; aTile++) {
				posNext = tileNFromSpace(w, aTile + 1, dir);
				w->mlink.tileOfPosition[pos] =
					w->mlink.tileOfPosition[posNext];
				drawTile(w, pos, False, False, FALSE, 0, 0);
				pos = posNext;
			}
			w->mlink.spacePosition = from;
			w->mlink.tileOfPosition[from] = 0;
			drawTile(w, w->mlink.spacePosition, True, True,
				FALSE, 0, 0);
		}
	}
	w->mlink.currentRef = -w->mlink.tileFaces;
#ifdef USE_SOUND
	if (w->mlink.sound) {
		playSound(BUMPSOUND);
	}
#endif
}

static void
rotateTiles(MlinkWidget w, int start, int finish, int direction, int fast)
{
	int newR, i, k, tempTile, currTile, pos, newPos;
	int r = 0, space = -1;
	Boolean	animate = (fast != INSTANT && w->mlink.delay > 0);

	w->mlink.currentRef = start;
	if (animate)
		animateRotate(w, start, finish, direction, fast);
	for (i = start; i <= finish; i++) {
		currTile = w->mlink.tileOfPosition[r * w->mlink.tiles + i];
		for (k = 1; k <= w->mlink.faces; k++) {
			newR = (direction == TOP) ?
				(r + w->mlink.faces - 1) % w->mlink.faces :
				(r + 1) % w->mlink.faces;
			pos = r * w->mlink.tiles + i;
			newPos = newR * w->mlink.tiles + i;
			tempTile = w->mlink.tileOfPosition[newPos];
			w->mlink.tileOfPosition[newPos] = currTile;
			currTile = tempTile;
			if (w->mlink.spacePosition == pos) {
				if (!animate)
					drawTile(w, newPos, True, True,
						FALSE, 0, 0);
				space = newPos;
			} else if (!animate)
				drawTile(w, newPos, False, False,
					FALSE, 0, 0);
			r = newR;
		}
		if (space >= 0)
			w->mlink.spacePosition = space;
	}
	/* Needed in case a window is on top. */
	if (animate) {
		int moveI = w->mlink.tileSize.x + w->mlink.delta.x;
		Pixmap dr = 0;

		FILLRECTANGLE(w, dr, w->mlink.inverseGC,
			start * moveI + w->mlink.puzzleOffset.x + 1, 0,
			(finish - start + 1) * moveI, w->core.height);
		drawFrame(w, dr, w->mlink.focus);
		drawAllTilesForColumns(w, start, finish);
	}
	w->mlink.currentRef = -w->mlink.tileFaces;
#ifdef USE_SOUND
	if (w->mlink.sound) {
		playSound(MOVESOUND);
	}
#endif
}

static Boolean
moveTilesDir(MlinkWidget w, int direction, int tile, Boolean all, int fast)
{
	int start, finish;

	start = tile;
	finish = tile;
	if (all) {
		start = 0;
		finish = w->mlink.tiles - 1;
	} else if (w->mlink.middle && tile > 0 && tile < w->mlink.tiles - 1) {
		start = 1;
		finish = w->mlink.tiles - 2;
	}
	switch (direction) {
	case TOP:
		rotateTiles(w, start, finish, TOP, fast);
		return True;
	case RIGHT:
		if (tile >= 0 && toRow(w, w->mlink.spacePosition) ==
				toRow(w, tile)) {
			moveTiles(w, tile, fast);
			return True;
		}
		break;
	case BOTTOM:
		rotateTiles(w, start, finish, BOTTOM, fast);
		return True;
	case LEFT:
		if (toRow(w, w->mlink.spacePosition) == toRow(w, tile)) {
			moveTiles(w, tile, fast);
			return True;
		}
		break;
	default:
		{
			char *buf;
			intCat(&buf, "moveTilesDir: direction ", direction);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
	return False;
}

static int
exchangeTiles(MlinkWidget w, int pos1, int pos2)
{
	int tempTile;

	if (w->mlink.tileOfPosition[pos1] <= 0)
		return FALSE;
	else if (w->mlink.tileOfPosition[pos2] <= 0)
		return FALSE;
	tempTile = w->mlink.tileOfPosition[pos1];
	w->mlink.tileOfPosition[pos1] = w->mlink.tileOfPosition[pos2];
	w->mlink.tileOfPosition[pos2] = tempTile;
	return TRUE;
}

static void
discreteMoves(MlinkWidget w, int from, int dir)
{
	moveTiles(w, from, NORMAL);
	setPuzzle(w, ACTION_MOVED);
	setMove(&undo, dir, from, 0);
	flushMoves(w, &redo, FALSE);
}

static void
moveShift(MlinkWidget w, int direction, int fast)
{
	(void) moveTilesDir(w, direction, 0, True, fast);
	setPuzzle(w, ACTION_CONTROL);
}

static void
controlPuzzle(MlinkWidget w, int direction)
{
	switch (direction) {
	case TOP:
		if (w->mlink.faces >= MAX_FACES)
			return;
		setPuzzle(w, ACTION_INCY);
		break;
	case RIGHT:
		setPuzzle(w, ACTION_INCX);
		break;
	case BOTTOM:
		if (w->mlink.faces <= MIN_FACES)
			return;
		setPuzzle(w, ACTION_DECY);
		break;
	case LEFT:
		if (w->mlink.tiles <= MIN_TILES)
			return;
		setPuzzle(w, ACTION_DECX);
		break;
	default:
		{
			char *buf;

			intCat(&buf, "ControlMlink: direction ",
				direction);
			DISPLAY_WARNING(buf);
			free(buf);
			setPuzzle(w, ACTION_IGNORE);
		}
	}
}

Boolean
movePuzzle(MlinkWidget 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(MlinkWidget w, int diff, int j, int shift)
{
	int k;

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

static void
movePosition(MlinkWidget 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(MlinkWidget w, int diff, int j, int shift)
{
	int k;

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

void
movePuzzleDelay(MlinkWidget w,
		const int direction, const int tile, const Boolean all)
{
	int n, pos;

	if (direction == RIGHT) {
		pos = w->mlink.spacePosition - tile;
		for (n = pos % w->mlink.tiles - 1; n >= 0; n--) {
			setPuzzle(w, ACTION_MOVED);
			setMove(&undo,
				direction, tile + n, False);
		}
	} else if (direction == LEFT) {
		pos = tile - w->mlink.spacePosition;
		for (n = pos % w->mlink.tiles - 1; n >= 0; n--) {
			setPuzzle(w, ACTION_MOVED);
			setMove(&undo,
				direction, tile - n, 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->mlink.delay);
}

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

static void
rotatePuzzle(MlinkWidget w, int direction, int tile)
{
	if (checkSolved(w) && !w->mlink.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(MlinkWidget w, int x, int direction, int shift, int control)
{
	int r;

	if (control != 0)
		controlPuzzle(w, direction);
	else 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->mlink.practice) {
		moveNoTiles(w);
		return;
	} else if (direction == LEFT || direction == RIGHT) {
		slidePuzzle(w, direction,
			w->mlink.spacePosition + direction - BOTTOM);
		if (checkSolved(w)) {
			setPuzzle(w, ACTION_SOLVED);
		}
	} else {
		if (!positionTile(w, x, &r))
			return;
		rotatePuzzle(w, direction, r);
		if (checkSolved(w)) {
			setPuzzle(w, ACTION_SOLVED);
		}
	}
}

static int
selectTiles(MlinkWidget w, int x, int y, int *i, int *j)
{
	int temp = w->mlink.faces * w->mlink.offset.y + w->mlink.delta.y - 1;

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

static void
selectSlideTiles(MlinkWidget w, int pos)
{
	int n;

	if (pos < 0) {
		if (w->mlink.delay > 0) {
			animateSlide(w, -pos, RIGHT, NORMAL, True);
#ifdef USE_SOUND
			if (w->mlink.sound) {
				playSound(BUMPSOUND);
			}
#endif
		} else {
			for (n = 1; n <= (-pos) % w->mlink.tiles; n++) {
				discreteMoves(w, w->mlink.spacePosition - 1,
					RIGHT);
			}
		}
	} else if (pos > 0) {
		if (w->mlink.delay > 0) {
			animateSlide(w, pos, LEFT, NORMAL, True);
#ifdef USE_SOUND
			if (w->mlink.sound) {
				playSound(BUMPSOUND);
			}
#endif
		} else {
			for (n = 1; n <= pos % w->mlink.tiles; n++) {
				discreteMoves(w, w->mlink.spacePosition + 1,
					LEFT);
			}
		}
	}
}

static void
resetTiles(MlinkWidget w)
{
	int i;

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

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

	w->mlink.spacePosition = w->mlink.tileFaces - 1;
	w->mlink.tileOfPosition[w->mlink.tileFaces - 1] = 0;
	for (i = 1; i < w->mlink.tileFaces; i++)
		w->mlink.tileOfPosition[i - 1] = i;
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->mlink.currentRef = -w->mlink.tileFaces;
	w->mlink.started = False;
}

static void
readFile(MlinkWidget w, FILE *fp, char *name)
{
	int c, i, tiles, faces, middle, 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->mlink.tiles; i < tiles; i++) {
			setPuzzle(w, ACTION_INCX);
		}
		for (i = w->mlink.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->mlink.faces; i < faces; i++) {
			setPuzzle(w, ACTION_INCY);
		}
		for (i = w->mlink.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", &middle) != 1) {
		(void) printf("corrupt record, expecting an integer for faces\n");
		return;
	}
	if (w->mlink.middle != (Boolean) middle) {
		setPuzzle(w, ACTION_MIDDLE);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &orient) != 1) {
		(void) printf("corrupt record, expecting an integer for orient\n");
		return;
	}
	if (w->mlink.orient != (Boolean) orient) {
		setPuzzle(w, ACTION_ORIENTIZE);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &practice) != 1) {
		(void) printf("corrupt record, expecting an integer for practice\n");
		return;
	}
	if (w->mlink.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) printf("corrupt record, expecting an integer for moves\n");
		return;
	}
	if (!scanStartPosition(fp, w))
		return;
	setPuzzle(w, ACTION_RESTORE);
	setStartPosition(w);
	if (!scanMoves(fp, w, moves))
		return;;
	(void) printf("%s: tiles %d, faces %d, middle %s, orient %s, ",
		name, tiles, faces, BOOL_STRING(middle), BOOL_STRING(orient));
	(void) printf("practice %s, moves %d\n",
		BOOL_STRING(practice), moves);
}

static void
getTiles(MlinkWidget 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->mlink.cheat = True; /* Assume the worst. */
}

static void
writeTiles(MlinkWidget 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->mlink.tiles);
	(void) fprintf(fp, "faces%c %d\n", SYMBOL, w->mlink.faces);
	(void) fprintf(fp, "middle%c %d\n", SYMBOL,
		(w->mlink.middle) ? 1 : 0);
	(void) fprintf(fp, "orient%c %d\n", SYMBOL,
		(w->mlink.orient) ? 1 : 0);
	(void) fprintf(fp, "practice%c %d\n", SYMBOL,
		(w->mlink.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(MlinkWidget w)
{
	if (madeMoves(&undo) &&
			w->mlink.currentRef == -w->mlink.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 (direction == LEFT)
				tile = w->mlink.spacePosition + 1;
			else if (direction == RIGHT)
				tile = w->mlink.spacePosition - 1;
			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(MlinkWidget w)
{
	if (madeMoves(&redo) &&
			w->mlink.currentRef == -w->mlink.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 (direction == LEFT)
				tile = w->mlink.spacePosition + 1;
			else if (direction == RIGHT)
				tile = w->mlink.spacePosition - 1;
			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(MlinkWidget w)
{
	if (w->mlink.currentRef != -w->mlink.tileFaces)
		return;
	resetTiles(w);
	drawAllTiles(w);
	setPuzzle(w, ACTION_RESET);
}

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

static void
randomizeTiles(MlinkWidget w)
{
	if (w->mlink.currentRef != -w->mlink.tileFaces)
		return;
	if (w->mlink.practice)
		practiceTiles(w);
	setPuzzle(w, ACTION_RESET);
	w->mlink.cheat = False;
	/* First interchange tiles an even number of times */
	if (w->mlink.tiles > 1 && w->mlink.faces > 1 && w->mlink.tileFaces > 4) {
		int pos, count = 0;

		for (pos = 0; pos < w->mlink.tileFaces; pos++) {
			int randomPos = pos;

			while (randomPos == pos)
				randomPos = NRAND(w->mlink.tileFaces);
			count += exchangeTiles(w, pos, randomPos);
		}
		if ((count & 1) == 1 &&
				!exchangeTiles(w, 0, 1) &&
				!exchangeTiles(w, w->mlink.tileFaces - 2,
				w->mlink.tileFaces - 1)) {
			DISPLAY_WARNING("randomizeTiles: should not get here");
		}
		drawAllTiles(w);
		FLUSH(w);
	}
	/* randomly position space */
	/* Idea for this came from "puzzle" by Don Bennett, HP Labs */
	{
		int n, s, e, c;

		/* order matters */
		s = toRow(w, w->mlink.spacePosition);
		c = toColumn(w, w->mlink.spacePosition);
		e = NRAND(w->mlink.faces);
		for (n = 0; n < e - s; n++)
			(void) moveTilesDir(w, BOTTOM, c, False, INSTANT);
		for (n = 0; n < s - e; n++)
			(void) moveTilesDir(w, TOP, c, False, INSTANT);
		s = toColumn(w, w->mlink.spacePosition);
		e = NRAND(w->mlink.tiles);
		for (n = 0; n < e - s; n++)
			(void) moveTilesDir(w, LEFT,
				w->mlink.spacePosition + 1, False, INSTANT);
		for (n = 0; n < s - e; n++)
			(void) moveTilesDir(w, RIGHT,
				w->mlink.spacePosition - 1, False, INSTANT);
		flushMoves(w, &undo, TRUE);
		flushMoves(w, &redo, FALSE);
		setPuzzle(w, ACTION_RANDOMIZE);
	}
#if 0
	/* Now move the space around randomly */
	if (w->mlink.tiles > 1 || w->mlink.faces > 1) {
		int big = w->mlink.tileFaces + NRAND(2);
		int lastDirection = -1;
		int randomDirection;

		setPuzzle(w, ACTION_RESET);

#ifdef DEBUG
		big = 3;
#endif
		if (big > 100)
			big = 100;
		while (big--) {
			randomDirection = NRAND(COORD);

#ifdef DEBUG
			sleep(1);
#endif
			if ((randomDirection + COORD / 2) % COORD != lastDirection ||
					w->mlink.tiles == 1 || w->mlink.faces == 1) {
				if (movePuzzle(w, randomDirection,
						NRAND(w->mlink.tiles),
						FALSE, False, INSTANT))
					lastDirection = randomDirection;
				else
					big++;
			}
		}
		flushMoves(w, &undo, TRUE);
		flushMoves(w, &redo, FALSE);
		setPuzzle(w, ACTION_RANDOMIZE);
	}
#endif
	if (checkSolved(w)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
solveTiles(MlinkWidget w)
{
	if (checkSolved(w) || w->mlink.currentRef != -w->mlink.tileFaces)
		return;
	if (w->mlink.tiles == 4 && w->mlink.faces == 4) {
		solveSomeTiles(w);
	} else {
		setPuzzle(w, ACTION_SOLVE_MESSAGE);
	}
}

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

static void
middleTiles(MlinkWidget w)
{
	setPuzzle(w, ACTION_MIDDLE);
}

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

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

static void
soundTiles(MlinkWidget w)
{
	w->mlink.sound = !w->mlink.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(MlinkWidget w)
{
	struct tagColor {
		int red, green, blue;
	} color;
	char szBuf[80], buf[20], charbuf[2];
	int face;

	w->mlink.tiles = (int) GetPrivateProfileInt(SECTION,
		"tiles", DEFAULT_TILES, INIFILE);
	w->mlink.faces = (int) GetPrivateProfileInt(SECTION,
		"faces", DEFAULT_FACES, INIFILE);
	w->mlink.orient = (BOOL) GetPrivateProfileInt(SECTION,
		"orient", DEFAULT_ORIENT, INIFILE);
	w->mlink.middle = (BOOL) GetPrivateProfileInt(SECTION,
		"middle", DEFAULT_MIDDLE, INIFILE);
	w->mlink.practice = (BOOL) GetPrivateProfileInt(SECTION,
		"practice", DEFAULT_PRACTICE, INIFILE);
	w->mlink.base = (int) GetPrivateProfileInt(SECTION,
		"base", DEFAULT_BASE, INIFILE);
	w->mlink.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->mlink.reverse = (BOOL) GetPrivateProfileInt(SECTION,
		"reverseVideo", DEFAULT_REVERSE, 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->mlink.frameGC = RGB(color.red, color.green, color.blue);
	/* black */
	(void) GetPrivateProfileString(SECTION,
		"tileColor", "0 0 0", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->mlink.tileGC[1] = RGB(color.red, color.green, color.blue);
	/* gray25 */
	(void) GetPrivateProfileString(SECTION,
		"tileBorder", "64 64 64", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->mlink.tileGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->mlink.tileGC[2] = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	w->mlink.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->mlink.inverseGC = RGB(color.red, color.green, color.blue);
	for (face = 0; face < MAX_FACES; 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->mlink.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->mlink.faceChar[face] = szBuf[0];
	}
	(void) GetPrivateProfileString(SECTION,
		"picture", PICTURE, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->mlink.picture, szBuf, 80);
	w->mlink.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", DEFAULT_DELAY, INIFILE);
	w->mlink.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", 0, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"bumpSound", BUMPSOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->mlink.bumpSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"moveSound", MOVESOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->mlink.moveSound, szBuf, 80);
	w->mlink.moveSound[80] = 0;
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->mlink.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->mlink.scoreFile, szBuf, 80);
}

void
destroyPuzzle(MlinkWidget w, HBRUSH brush)
{
	if (w->core.memDC) {
		if (w->mlink.bufferTiles != NULL) {
			DeleteObject(w->mlink.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(MlinkWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = (w->mlink.colormap == None) ?
		w->mlink.oldColormap : w->mlink.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(MlinkWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = (w->mlink.colormap == None) ?
		w->mlink.oldColormap : w->mlink.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(MlinkWidget w, int face)
{
	XGCValues values;
	XtGCMask valueMask;
	XColor colorCell, rgb;
	Colormap colormap = (w->mlink.colormap == None) ?
		w->mlink.oldColormap : w->mlink.colormap;

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

			stringCat(&buf1, "Color name \"",
				w->mlink.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->mlink.reverse) {
		values.background = w->mlink.background;
		values.foreground = w->mlink.foreground;
	} else {
		values.background = w->mlink.foreground;
		values.foreground = w->mlink.background;
	}
	if (w->mlink.faceGC[face])
		XtReleaseGC((Widget) w, w->mlink.faceGC[face]);
	w->mlink.faceGC[face] = XtGetGC((Widget) w, valueMask, &values);
	if (w->mlink.fontInfo)
		XSetFont(XtDisplay(w), w->mlink.faceGC[face],
			w->mlink.fontInfo->fid);
}

static void
setAllColors(MlinkWidget w)
{
	XGCValues values;
	XtGCMask valueMask;
	int face;

	valueMask = GCForeground | GCBackground;

	if (w->mlink.reverse) {
		values.foreground = w->mlink.foreground;
		values.background = w->mlink.background;
	} else {
		values.foreground = w->mlink.background;
		values.background = w->mlink.foreground;
	}
	if (w->mlink.inverseGC)
		XtReleaseGC((Widget) w, w->mlink.inverseGC);
	w->mlink.inverseGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->mlink.mono) {
		if (w->mlink.reverse) {
			values.foreground = w->mlink.background;
			values.background = w->mlink.foreground;
		} else {
			values.foreground = w->mlink.foreground;
			values.background = w->mlink.background;
		}
	} else {
		values.foreground = w->mlink.frameColor;
		values.background = w->mlink.borderColor;
	}
	if (w->mlink.frameGC)
		XtReleaseGC((Widget) w, w->mlink.frameGC);
	w->mlink.frameGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->mlink.mono) {
		if (w->mlink.reverse) {
			values.background = w->mlink.foreground;
			values.foreground = w->mlink.background;
		} else {
			values.foreground = w->mlink.foreground;
			values.background = w->mlink.background;
		}
	} else {
		values.foreground = w->mlink.tileColor;
		values.background = w->mlink.borderColor;
	}
	if (w->mlink.tileGC[1])
		XtReleaseGC((Widget) w, w->mlink.tileGC[1]);
	w->mlink.tileGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->mlink.mono) {
		values.foreground = darker(w, w->mlink.tileColor);
	}
	if (w->mlink.tileGC[0])
		XtReleaseGC((Widget) w, w->mlink.tileGC[0]);
	w->mlink.tileGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->mlink.mono) {
		values.foreground = brighter(w, w->mlink.tileColor);
	}
	if (w->mlink.tileGC[2])
		XtReleaseGC((Widget) w, w->mlink.tileGC[2]);
	w->mlink.tileGC[2] = XtGetGC((Widget) w, valueMask, &values);
	if (w->mlink.mono) {
		if (w->mlink.reverse) {
			values.foreground = w->mlink.foreground;
			values.background = w->mlink.background;
		} else {
			values.background = w->mlink.foreground;
			values.foreground = w->mlink.background;
		}
	} else {
		values.foreground = w->mlink.borderColor;
		values.background = w->mlink.tileColor;
	}
	if (w->mlink.borderGC)
		XtReleaseGC((Widget) w, w->mlink.borderGC);
	w->mlink.borderGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->mlink.fontInfo)
		XSetFont(XtDisplay(w), w->mlink.borderGC,
			w->mlink.fontInfo->fid);
	for (face = 0; face < MAX_FACES; face++)
		getColor(w, face);
}

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

	checkTiles(w);
	for (face = 0; face < MAX_FACES; face++) {
		if (strcmp(w->mlink.faceName[face], c->mlink.faceName[face])) {
			setColors = True;
			break;
		}
	}
	if (w->mlink.font != c->mlink.font ||
			w->mlink.borderColor != c->mlink.borderColor ||
			w->mlink.reverse != c->mlink.reverse ||
			w->mlink.mono != c->mlink.mono ||
			setColors) {
		loadFont(w);
		setAllColors(w);
		redrawTiles = True;
	} else if (w->mlink.background != c->mlink.background ||
			w->mlink.foreground != c->mlink.foreground ||
			w->mlink.tileColor != c->mlink.tileColor) {
		setAllColors(w);
		redrawTiles = True;
	}
	if (w->mlink.tiles != c->mlink.tiles ||
			w->mlink.faces != c->mlink.faces ||
			w->mlink.orient != c->mlink.orient ||
			w->mlink.middle != c->mlink.middle ||
			w->mlink.practice != c->mlink.practice ||
			w->mlink.base != c->mlink.base) {
		sizePuzzle(w);
		redraw = True;
	} else if (w->mlink.offset.x != c->mlink.offset.x ||
			w->mlink.offset.y != c->mlink.offset.y) {
		resizePuzzle(w);
		redraw = True;
	}
	if (w->mlink.delay != c->mlink.delay) {
		w->mlink.numSlices = ((w->mlink.delay < MAX_SLICES) ?
			w->mlink.delay + 1 : MAX_SLICES);
	}
	if (w->mlink.menu != ACTION_IGNORE) {
		int menu = w->mlink.menu;

		w->mlink.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_MIDDLE:
			middleTiles(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->mlink.focus)
			drawFrame(w, 0, True);
		drawAllTiles(w);
	}
	return (redraw);
}

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

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

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

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

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

void
resizePuzzle(MlinkWidget 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->mlink.delta.x = 1;
	w->mlink.delta.y = 1;
	w->mlink.offset.x = MAX(((int) w->core.width - 2) /
		w->mlink.tiles, 0);
	w->mlink.offset.y = MAX((int) w->core.height /
		w->mlink.faces - 2, 0);
	w->mlink.faceSize.x = w->mlink.offset.x * w->mlink.tiles +
		w->mlink.delta.x + 2;
	w->mlink.faceSize.y = w->mlink.offset.y + w->mlink.delta.y + 2;
	w->mlink.puzzleSize.x = w->mlink.faceSize.x;
	w->mlink.puzzleSize.y = (w->mlink.faceSize.y - 2) *
		w->mlink.faces + 2;
	w->mlink.puzzleOffset.x = ((int) w->core.width -
		w->mlink.puzzleSize.x + 2) / 2;
	w->mlink.puzzleOffset.y = ((int) w->core.height -
		w->mlink.puzzleSize.y + 2) / 2;
	w->mlink.tileSize.x = MAX(w->mlink.offset.x - w->mlink.delta.x, 0);
	w->mlink.tileSize.y = MAX(w->mlink.offset.y - w->mlink.delta.y, 0);
}

#ifndef WINVER
static
#endif
void
sizePuzzle(MlinkWidget w)
{
	resetTiles(w);
	resizePuzzle(w);
}

#ifndef WINVER
static
#endif
void
initializePuzzle(
#ifdef WINVER
MlinkWidget w, HBRUSH brush
#else
Widget request, Widget renew
#endif
)
{
#ifdef WINVER
	setValuesPuzzle(w);
	brush = CreateSolidBrush(w->mlink.inverseGC);
	SETBACK(w->core.hWnd, brush);
	(void) SRAND(time(NULL));
	w->mlink.bufferTiles[0] = NULL;
	w->mlink.bufferTiles[1] = NULL;
#else
	MlinkWidget w = (MlinkWidget) renew;
	int i;

	(void) SRAND(getpid());
	w->mlink.bufferTiles[0] = None;
	w->mlink.bufferTiles[1] = None;
	w->mlink.colormap = None;
	w->mlink.oldColormap = None;
	w->mlink.fontInfo = NULL;
	for (i = 0; i < MAX_FACES; i++)
		w->mlink.faceGC[i] = NULL;
	for (i = 0; i < FG_SHADES; i++)
		w->mlink.tileGC[i] = NULL;
	w->mlink.borderGC = NULL;
	w->mlink.frameGC = NULL;
	w->mlink.inverseGC = NULL;
	w->mlink.image = NULL;
#endif
	w->mlink.focus = False;
	loadFont(w);
	w->mlink.tileOfPosition = NULL;
	checkTiles(w);
	newMoves(&undo);
	newMoves(&redo);
	w->mlink.numSlices = ((w->mlink.delay < MAX_SLICES) ?
		w->mlink.delay + 1 : MAX_SLICES);
	w->mlink.cheat = False;
	sizePuzzle(w);
#ifdef USE_SOUND
#ifdef USE_NAS
	dsp = XtDisplay(w);
#endif
#ifdef USE_ESOUND
	(void) init_sound();
#endif
#endif
}

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

	if (!w->core.visible)
		return;
#endif
	resizeTiles(w);
	eraseFrame(w, 0);
	drawFrame(w, 0, w->mlink.focus);
	drawAllTiles(w);
}

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

#ifndef WINVER
static
#endif
void
selectPuzzle(MlinkWidget 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->mlink.motion = False;
	w->mlink.motionDiff = 0;
	w->mlink.shiftDiff = 0;
	pos = selectTiles(w, x, y, &i, &j);
	if (-w->mlink.tileFaces != pos) {
		w->mlink.currentTile = i;
		w->mlink.currentFace = j;
		w->mlink.currentRef = pos;
		if (shift != 0 || w->mlink.practice || !checkSolved(w)) {
			pos = w->mlink.currentTile +
				w->mlink.currentFace * w->mlink.tiles;
			drawTile(w, pos, (w->mlink.tileOfPosition[i +
				j * w->mlink.tiles] <= 0),
				False, TRUE, 0, 0);
		}
	} else
		w->mlink.currentRef = -w->mlink.tileFaces;
}

#ifndef WINVER
static
#endif
void
motionPuzzle(MlinkWidget 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->mlink.currentRef == -w->mlink.tileFaces)
		return;
	if (shift == 0 && !w->mlink.practice && checkSolved(w)) {
		moveNoTiles(w);
		w->mlink.currentRef = -w->mlink.tileFaces;
		return;
	}
	pos = selectTiles(w, x, y, &i, &j);
	if (w->mlink.currentTile != i) {
		int k = w->mlink.currentTile +
			w->mlink.currentFace * w->mlink.tiles;

		drawTile(w, k, True, True, TRUE, 0, 0);
		drawTile(w, k, (w->mlink.tileOfPosition[k] <= 0),
			(w->mlink.tileOfPosition[k] <= 0), FALSE, 0, 0);
		diffPosition(w, w->mlink.motionDiff, j, FALSE);
		diffPosition(w, w->mlink.shiftDiff, j, TRUE);
		if (shift == 0 && checkSolved(w)) {
			setPuzzle(w, ACTION_SOLVED);
		}
		w->mlink.currentTile = i;
		w->mlink.currentFace = j;
		w->mlink.currentRef = pos;
		w->mlink.motionDiff = 0;
		w->mlink.shiftDiff = 0;
	}
	if (-w->mlink.tileFaces != pos) {
		if (j != w->mlink.currentFace) {
			diff = (w->mlink.currentFace - j + w->mlink.faces) %
				w->mlink.faces;
			diffMove(w, diff, j, shift);
			if (shift == 0 && checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
			w->mlink.motion = True;
			if (shift == 0)
				w->mlink.motionDiff = (diff +
					w->mlink.motionDiff) % w->mlink.faces;
			else
				w->mlink.shiftDiff = (diff +
					w->mlink.shiftDiff) % w->mlink.faces;
		}
		w->mlink.currentTile = i;
		w->mlink.currentFace = j;
		w->mlink.currentRef = pos;
		if (shift != 0 || w->mlink.practice || !checkSolved(w)) {
			pos = w->mlink.currentTile +
				w->mlink.currentFace * w->mlink.tiles;
			drawTile(w, pos, (w->mlink.tileOfPosition[i +
				j * w->mlink.tiles] <= 0),
				False, TRUE, 0, 0);
		}
	} else
		w->mlink.currentRef = -w->mlink.tileFaces;
}

#ifndef WINVER
static
#endif
void
releasePuzzle(MlinkWidget 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->mlink.currentRef == -w->mlink.tileFaces)
		return;
	pos = w->mlink.currentTile + w->mlink.currentFace * w->mlink.tiles;
	drawTile(w, pos, True, True, TRUE, 0, 0);
	if (w->mlink.tileOfPosition[pos] > 0) {
		drawTile(w, pos, False, False, FALSE, 0, 0);
	}
	if (shift == 0 && !w->mlink.practice && checkSolved(w)) {
		moveNoTiles(w);
		w->mlink.currentRef = -w->mlink.tileFaces;
		return;
	}
	pos = selectTiles(w, x, y, &i, &j);
	if (-w->mlink.tileFaces != pos) {
		if (j == w->mlink.currentFace && !w->mlink.motion) {
			pos = w->mlink.currentRef;
			if (pos / w->mlink.tiles == 0 &&
					j == toRow(w, w->mlink.spacePosition) &&
					pos != 0) {
				if (shift != 0 && checkSolved(w))
					moveNoTiles(w);
				else {
					selectSlideTiles(w, pos);
					w->mlink.currentTile = w->mlink.tiles;
					if (checkSolved(w)) {
						setPuzzle(w, ACTION_SOLVED);
					}
				}
			}
		} else {
			diff = (w->mlink.currentFace - j + w->mlink.faces) %
				w->mlink.faces;
			diffMove(w, diff, j, shift);
			if (shift == 0)
				w->mlink.motionDiff = (diff +
					w->mlink.motionDiff) %
					w->mlink.faces;
			else
				w->mlink.shiftDiff = (diff +
					w->mlink.shiftDiff) % w->mlink.faces;
			diffPosition(w, w->mlink.motionDiff, j, FALSE);
			diffPosition(w, w->mlink.shiftDiff, j, TRUE);
			if (shift == 0 && checkSolved(w)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		}
	}
	w->mlink.currentRef = -w->mlink.tileFaces;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

#ifndef WINVER
static
#endif
void
middlePuzzle(MlinkWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	middleTiles(w);
}

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

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

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

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

#ifndef WINVER
static
#endif
void
leavePuzzle(MlinkWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->mlink.focus = False;
	drawFrame(w, 0, w->mlink.focus);
#ifndef WINVER
	if (w->mlink.colormap != None)
		XInstallColormap(XtDisplay(w), w->mlink.oldColormap);
#endif

}

#ifndef WINVER
static void
movePuzzleTop(MlinkWidget w , XEvent *event, char **args, int nArgs
)
{
	int x = event->xbutton.x;
	int shift = (int) (event->xkey.state & (ShiftMask | LockMask));
	int control = (int) (event->xkey.state & ControlMask);

	movePuzzleInput(w, x, TOP, shift, control);
}

static void
movePuzzleBottom(MlinkWidget w , XEvent *event, char **args, int nArgs
)
{
	int x = event->xbutton.x;
	int shift = (int) (event->xkey.state & (ShiftMask | LockMask));
	int control = (int) (event->xkey.state & ControlMask);

	movePuzzleInput(w, x, BOTTOM, shift, control);
}

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

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