1 /*-
2 # X-BASED MISSING LINK(tm)
3 #
4 #  Mlink.c
5 #
6 ###
7 #
8 #  Copyright (c) 1994 - 99	David Albert Bagley, bagleyd@tux.org
9 #
10 #                   All Rights Reserved
11 #
12 #  Permission to use, copy, modify, and distribute this software and
13 #  its documentation for any purpose and without fee is hereby granted,
14 #  provided that the above copyright notice appear in all copies and
15 #  that both that copyright notice and this permission notice appear in
16 #  supporting documentation, and that the name of the author not be
17 #  used in advertising or publicity pertaining to distribution of the
18 #  software without specific, written prior permission.
19 #
20 #  This program is distributed in the hope that it will be "playable",
21 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
22 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23 #
24 */
25 
26 /* Methods file for Mlink */
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #ifdef VMS
31 #include <unixlib.h>
32 #else
33 #if HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #endif
37 #include <X11/IntrinsicP.h>
38 #include <X11/Intrinsic.h>
39 #include <X11/StringDefs.h>
40 #include <X11/CoreP.h>
41 #include "MlinkP.h"
42 
43 #ifndef DATAFILE
44 #define DATAFILE "/usr/games/lib/mlink.data"
45 #endif
46 
47 static void InitializeMlink(Widget request, Widget renew);
48 static void ExposeMlink(Widget renew, XEvent * event, Region region);
49 static void ResizeMlink(MlinkWidget w);
50 static void DestroyMlink(Widget old);
51 static Boolean SetValuesMlink(Widget current, Widget request, Widget renew);
52 static void QuitMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
53 static void MoveMlinkTop(MlinkWidget w, XEvent * event, char **args, int nArgs);
54 static void MoveMlinkLeft(MlinkWidget w, XEvent * event, char **args, int nArgs);
55 static void MoveMlinkRight(MlinkWidget w, XEvent * event, char **args, int nArgs);
56 static void MoveMlinkBottom(MlinkWidget w, XEvent * event, char **args, int nArgs);
57 static void MoveMlinkInput(MlinkWidget w, int x, int direction, int shift, int control);
58 static void SelectMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
59 static void ReleaseMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
60 static void RandomizeMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
61 static void RandomizeMlinkMaybe(MlinkWidget w, XEvent * event, char **args, int nArgs);
62 static void GetMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
63 static void WriteMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
64 static void UndoMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
65 static void SolveMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
66 static void OrientizeMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
67 static void MiddleMlink(MlinkWidget w, XEvent * event, char **args, int nArgs);
68 static void ControlMlink(MlinkWidget w, int direction);
69 static void RotateMlink(MlinkWidget w, int direction, int tile);
70 static void SlideMlink(MlinkWidget w, int direction);
71 
72 static int  SelectTiles(MlinkWidget w, int x, int y, int *i, int *j);
73 static void SelectSlideTiles(MlinkWidget w, int pos);
74 static void SetAllColors(MlinkWidget w, Boolean init);
75 static void GetColor(MlinkWidget w, int face, Boolean init);
76 static void MoveShiftCb(MlinkWidget w, int direction);
77 static void CheckTiles(MlinkWidget w);
78 static void ResetTiles(MlinkWidget w);
79 static void ResizeTiles(MlinkWidget w);
80 static void MoveNoTiles(MlinkWidget w);
81 static Boolean MoveTilesDir(MlinkWidget w, int direction, int tile);
82 static void RandomizeTiles(MlinkWidget w);
83 static void MoveTiles(MlinkWidget w, int from);
84 static void RotateTiles(MlinkWidget w, int c, int direction);
85 static int  ExchangeTiles(MlinkWidget w, int pos1, int pos2);
86 static void DrawFrame(MlinkWidget w, GC gc);
87 static void DrawTile(MlinkWidget w, int pos, Boolean blank, Boolean erase, int offset);
88 static void DrawLink(MlinkWidget w, GC gc, int pos, int offset);
89 static Boolean PositionTile(MlinkWidget w, int x, int *r);
90 static int  Row(MlinkWidget w, int pos);
91 static int  Column(MlinkWidget w, int pos);
92 static int  int2String(char *buf, int number, int base, Boolean capital);
93 
94 static char defaultTranslationsMlink[] =
95 "<KeyPress>q: Quit()\n\
96    Ctrl<KeyPress>C: Quit()\n\
97    <KeyPress>Up: MoveTop()\n\
98    <KeyPress>KP_8: MoveTop()\n\
99    <KeyPress>R8: MoveTop()\n\
100    <KeyPress>Left: MoveLeft()\n\
101    <KeyPress>KP_4: MoveLeft()\n\
102    <KeyPress>R10: MoveLeft()\n\
103    <KeyPress>Right: MoveRight()\n\
104    <KeyPress>KP_6: MoveRight()\n\
105    <KeyPress>R12: MoveRight()\n\
106    <KeyPress>Down: MoveBottom()\n\
107    <KeyPress>KP_2: MoveBottom()\n\
108    <KeyPress>R14: MoveBottom()\n\
109    <Btn1Down>: Select()\n\
110    <Btn1Up>: Release()\n\
111    <KeyPress>r: Randomize()\n\
112    <Btn3Down>(2+): Randomize()\n\
113    <Btn3Down>: RandomizeMaybe()\n\
114    <KeyPress>g: Get()\n\
115    <KeyPress>w: Write()\n\
116    <KeyPress>u: Undo()\n\
117    <KeyPress>s: Solve()\n\
118    <KeyPress>o: Orientize()\n\
119    <KeyPress>m: Middle()";
120 
121 static XtActionsRec actionsListMlink[] =
122 {
123 	{"Quit", (XtActionProc) QuitMlink},
124 	{"MoveTop", (XtActionProc) MoveMlinkTop},
125 	{"MoveLeft", (XtActionProc) MoveMlinkLeft},
126 	{"MoveRight", (XtActionProc) MoveMlinkRight},
127 	{"MoveBottom", (XtActionProc) MoveMlinkBottom},
128 	{"Select", (XtActionProc) SelectMlink},
129 	{"Release", (XtActionProc) ReleaseMlink},
130 	{"Randomize", (XtActionProc) RandomizeMlink},
131 	{"RandomizeMaybe", (XtActionProc) RandomizeMlinkMaybe},
132 	{"Get", (XtActionProc) GetMlink},
133 	{"Write", (XtActionProc) WriteMlink},
134 	{"Undo", (XtActionProc) UndoMlink},
135 	{"Solve", (XtActionProc) SolveMlink},
136 	{"Orientize", (XtActionProc) OrientizeMlink},
137 	{"Middle", (XtActionProc) MiddleMlink}
138 };
139 
140 static XtResource resourcesMlink[] =
141 {
142 	{XtNuserName, XtCUserName, XtRString, sizeof (String),
143 	 XtOffset(MlinkWidget, mlink.username), XtRString, "nobody"},
144 	{XtNfaceColor0, XtCLabel, XtRString, sizeof (String),
145 	 XtOffset(MlinkWidget, mlink.faceName[0]), XtRString, "White"},
146 	{XtNfaceColor1, XtCLabel, XtRString, sizeof (String),
147 	 XtOffset(MlinkWidget, mlink.faceName[1]), XtRString, "Yellow"},
148 	{XtNfaceColor2, XtCLabel, XtRString, sizeof (String),
149 	 XtOffset(MlinkWidget, mlink.faceName[2]), XtRString, "Green"},
150 	{XtNfaceColor3, XtCLabel, XtRString, sizeof (String),
151 	 XtOffset(MlinkWidget, mlink.faceName[3]), XtRString, "Red"},
152 	{XtNfaceColor4, XtCLabel, XtRString, sizeof (String),
153 	 XtOffset(MlinkWidget, mlink.faceName[4]), XtRString, "Blue"},
154 	{XtNfaceColor5, XtCLabel, XtRString, sizeof (String),
155 	 XtOffset(MlinkWidget, mlink.faceName[5]), XtRString, "Magenta"},
156 	{XtNfaceColor6, XtCLabel, XtRString, sizeof (String),
157 	 XtOffset(MlinkWidget, mlink.faceName[6]), XtRString, "Cyan"},
158 	{XtNfaceColor7, XtCLabel, XtRString, sizeof (String),
159 	 XtOffset(MlinkWidget, mlink.faceName[7]), XtRString, "Orange"},
160 	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
161     XtOffset(MlinkWidget, mlink.foreground), XtRString, XtDefaultForeground},
162 	{XtNtileColor, XtCBackground, XtRPixel, sizeof (Pixel),
163      XtOffset(MlinkWidget, mlink.tileColor), XtRString, XtDefaultForeground},
164 	{XtNtileBorder, XtCForeground, XtRPixel, sizeof (Pixel),
165    XtOffset(MlinkWidget, mlink.borderColor), XtRString, XtDefaultBackground},
166 	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
167 	 XtOffset(MlinkWidget, core.width), XtRString, "200"},
168 	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
169 	 XtOffset(MlinkWidget, core.height), XtRString, "200"},
170 	{XtNtiles, XtCTiles, XtRInt, sizeof (int),
171 	 XtOffset(MlinkWidget, mlink.tiles), XtRString, "4"},	/* DEFAULTTILES */
172 	{XtNfaces, XtCFaces, XtRInt, sizeof (int),
173 	 XtOffset(MlinkWidget, mlink.faces), XtRString, "4"},	/* DEFAULTFACES */
174 	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
175 	 XtOffset(MlinkWidget, mlink.orient), XtRString, "FALSE"},	/*DEFAULTORIENT */
176 	{XtNmiddle, XtCMiddle, XtRBoolean, sizeof (Boolean),
177 	 XtOffset(MlinkWidget, mlink.middle), XtRString,
178 	 "TRUE"},		/*DEFAULTMIDDLE */
179 	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
180 	 XtOffset(MlinkWidget, mlink.mono), XtRString, "FALSE"},
181 	{XtNreverse, XtCReverse, XtRBoolean, sizeof (Boolean),
182 	 XtOffset(MlinkWidget, mlink.reverse), XtRString, "FALSE"},
183 	{XtNbase, XtCBase, XtRInt, sizeof (int),
184 	 XtOffset(MlinkWidget, mlink.base), XtRString, "10"},
185 	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
186 	 XtOffset(MlinkWidget, mlink.started), XtRString, "FALSE"},
187 	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
188 	 XtOffset(MlinkWidget, mlink.select), XtRCallback, NULL}
189 };
190 
191 MlinkClassRec mlinkClassRec =
192 {
193 	{
194 		(WidgetClass) & widgetClassRec,		/* superclass */
195 		"Mlink",	/* class name */
196 		sizeof (MlinkRec),	/* widget size */
197 		NULL,		/* class initialize */
198 		NULL,		/* class part initialize */
199 		FALSE,		/* class inited */
200 		(XtInitProc) InitializeMlink,	/* initialize */
201 		NULL,		/* initialize hook */
202 		XtInheritRealize,	/* realize */
203 		actionsListMlink,	/* actions */
204 		XtNumber(actionsListMlink),	/* num actions */
205 		resourcesMlink,	/* resources */
206 		XtNumber(resourcesMlink),	/* num resources */
207 		NULLQUARK,	/* xrm class */
208 		TRUE,		/* compress motion */
209 		TRUE,		/* compress exposure */
210 		TRUE,		/* compress enterleave */
211 		TRUE,		/* visible interest */
212 		(XtWidgetProc) DestroyMlink,	/* destroy */
213 		(XtWidgetProc) ResizeMlink,	/* resize */
214 		(XtExposeProc) ExposeMlink,	/* expose */
215 		(XtSetValuesFunc) SetValuesMlink,	/* set values */
216 		NULL,		/* set values hook */
217 		XtInheritSetValuesAlmost,	/* set values almost */
218 		NULL,		/* get values hook */
219 		NULL,		/* accept focus */
220 		XtVersion,	/* version */
221 		NULL,		/* callback private */
222 		defaultTranslationsMlink,	/* tm table */
223 		NULL,		/* query geometry */
224 		NULL,		/* display accelerator */
225 		NULL		/* extension */
226 	},
227 	{
228 		0		/* ignore */
229 	}
230 };
231 
232 WidgetClass mlinkWidgetClass = (WidgetClass) & mlinkClassRec;
233 
234 static void
InitializeMlink(Widget request,Widget renew)235 InitializeMlink(Widget request, Widget renew)
236 {
237 	MlinkWidget w = (MlinkWidget) renew;
238 
239 	w->mlink.tileOfPosition = NULL;
240 	CheckTiles(w);
241 	InitMoves();
242 	ResetTiles(w);
243 	(void) SRAND(getpid());
244 	w->mlink.depth = DefaultDepthOfScreen(XtScreen(w));
245 	SetAllColors(w, True);
246 	ResizeMlink(w);
247 }
248 
249 static void
DestroyMlink(Widget old)250 DestroyMlink(Widget old)
251 {
252 	MlinkWidget w = (MlinkWidget) old;
253 	int         face;
254 
255 	for (face = 0; face < MAXFACES; face++)
256 		XtReleaseGC(old, w->mlink.faceGC[face]);
257 	XtReleaseGC(old, w->mlink.tileGC);
258 	XtReleaseGC(old, w->mlink.puzzleGC);
259 	XtReleaseGC(old, w->mlink.inverseGC);
260 	XtRemoveCallbacks(old, XtNselectCallback, w->mlink.select);
261 }
262 
263 static void
ResizeMlink(MlinkWidget w)264 ResizeMlink(MlinkWidget w)
265 {
266 	w->mlink.delta.x = 3;
267 	w->mlink.delta.y = 3;
268 	w->mlink.offset.x = MAX(((int) w->core.width - w->mlink.delta.x) /
269 				w->mlink.tiles, 0);
270 	w->mlink.offset.y = MAX(((int) w->core.height - w->mlink.delta.y) /
271 				w->mlink.faces, 0);
272 	w->mlink.faceSize.x = w->mlink.offset.x * w->mlink.tiles +
273 		w->mlink.delta.x;
274 	w->mlink.faceSize.y = w->mlink.offset.y + w->mlink.delta.y;
275 	w->mlink.puzzleSize.x = w->mlink.faceSize.x + w->mlink.delta.x;
276 	w->mlink.puzzleSize.y = (w->mlink.faceSize.y - w->mlink.delta.y) *
277 		w->mlink.faces + w->mlink.delta.y;
278 	w->mlink.puzzleOffset.x = ((int) w->core.width -
279 				   w->mlink.puzzleSize.x + 2) / 2;
280 	w->mlink.puzzleOffset.y = ((int) w->core.height -
281 				   w->mlink.puzzleSize.y + 2) / 2;
282 	w->mlink.tileSize.x = MAX(w->mlink.offset.x - w->mlink.delta.x, 0);
283 	w->mlink.tileSize.y = MAX(w->mlink.offset.y - w->mlink.delta.y, 0);
284 	ResizeTiles(w);
285 }
286 
287 static void
ExposeMlink(Widget renew,XEvent * event,Region region)288 ExposeMlink(Widget renew, XEvent * event, Region region)
289 {
290 	MlinkWidget w = (MlinkWidget) renew;
291 
292 	if (w->core.visible) {
293 		if (w->mlink.reverse)
294 			XFillRectangle(XtDisplay(w), XtWindow(w),
295 				       w->mlink.inverseGC, 0, 0, w->core.width, w->core.height);
296 		DrawFrame(w, w->mlink.puzzleGC);
297 		DrawAllTiles(w);
298 	}
299 }
300 
301 static      Boolean
SetValuesMlink(Widget current,Widget request,Widget renew)302 SetValuesMlink(Widget current, Widget request, Widget renew)
303 {
304 	MlinkWidget c = (MlinkWidget) current, w = (MlinkWidget) renew;
305 	Boolean     redraw = False, setColors = False;
306 	Boolean     redrawTiles = False;
307 	int         face;
308 
309 	CheckTiles(w);
310 	for (face = 0; face < MAXFACES; face++) {
311 		if (strcmp(w->mlink.faceName[face], c->mlink.faceName[face])) {
312 			setColors = True;
313 			break;
314 		}
315 	}
316 	if (w->core.background_pixel != c->core.background_pixel ||
317 	    w->mlink.foreground != c->mlink.foreground ||
318 	    w->mlink.borderColor != c->mlink.borderColor ||
319 	    w->mlink.tileColor != c->mlink.tileColor ||
320 	    w->mlink.reverse != c->mlink.reverse ||
321 	    w->mlink.mono != c->mlink.mono ||
322 	    setColors) {
323 		SetAllColors(w, False);
324 		redrawTiles = True;
325 	}
326 	if (w->mlink.tiles != c->mlink.tiles ||
327 	    w->mlink.faces != c->mlink.faces ||
328 	    w->mlink.orient != c->mlink.orient ||
329 	    w->mlink.middle != c->mlink.middle ||
330 	    w->mlink.base != c->mlink.base) {
331 		ResetTiles(w);
332 		ResizeMlink(w);
333 		redraw = True;
334 	} else if (w->mlink.offset.x != c->mlink.offset.x ||
335 		   w->mlink.offset.y != c->mlink.offset.y) {
336 		ResizeMlink(w);
337 		redraw = True;
338 	}
339 	if (redrawTiles && !redraw && XtIsRealized(renew) && renew->core.visible) {
340 		DrawFrame(w, c->mlink.inverseGC);
341 		DrawFrame(w, w->mlink.puzzleGC);
342 		DrawAllTiles(w);
343 	}
344 	return (redraw);
345 }
346 
347 static void
QuitMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)348 QuitMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
349 {
350 	XtCloseDisplay(XtDisplay(w));
351 	exit(0);
352 }
353 
354 static void
SelectMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)355 SelectMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
356 {
357 	int         i, j, pos, shift;
358 
359 	pos = SelectTiles(w, event->xbutton.x, event->xbutton.y, &i, &j);
360 	if (-w->mlink.tileFaces != pos) {
361 		w->mlink.currentTile = i;
362 		w->mlink.currentFace = j;
363 		w->mlink.currentRef = pos;
364 		shift = (int) (event->xkey.state & (ShiftMask | LockMask));
365 		if (shift || !CheckSolved(w)) {
366 			pos = w->mlink.currentTile + w->mlink.currentFace * w->mlink.tiles;
367 			DrawTile(w, pos, (w->mlink.tileOfPosition[i + j * w->mlink.tiles] <= 0),
368 				 False, TRUE);
369 		}
370 	} else
371 		w->mlink.currentRef = -w->mlink.tileFaces;
372 }
373 
374 static void
ReleaseMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)375 ReleaseMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
376 {
377 	int         i, j, pos, diff, shift;
378 
379 	if (w->mlink.currentRef == -w->mlink.tileFaces)
380 		return;
381 	pos = w->mlink.currentTile + w->mlink.currentFace * w->mlink.tiles;
382 	DrawTile(w, pos, (w->mlink.tileOfPosition[pos] <= 0), True, TRUE);
383 	if (w->mlink.tileOfPosition[pos] > 0) {
384 		DrawTile(w, pos, False, False, FALSE);
385 	}
386 	shift = (int) (event->xkey.state & (ShiftMask | LockMask));
387 	if (!shift && CheckSolved(w))
388 		MoveNoTiles(w);
389 	else {
390 		pos = SelectTiles(w, event->xbutton.x, event->xbutton.y, &i, &j);
391 		if (-w->mlink.tileFaces != pos) {
392 			if (j == w->mlink.currentFace) {
393 				pos = w->mlink.currentRef;
394 				if (pos / w->mlink.tiles == 0 &&
395 				    j == Column(w, w->mlink.spacePosition) && pos != 0) {
396 					if (shift && CheckSolved(w))
397 						MoveNoTiles(w);
398 					else {
399 						SelectSlideTiles(w, pos);
400 						w->mlink.currentTile = w->mlink.tiles;
401 						if (CheckSolved(w)) {
402 							mlinkCallbackStruct cb;
403 
404 							cb.reason = MLINK_SOLVED;
405 							XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
406 						}
407 					}
408 				}
409 			} else {
410 				diff = (w->mlink.currentFace - j + w->mlink.faces) % w->mlink.faces;
411 				if (diff > w->mlink.faces / 2)
412 					for (i = 0; i < w->mlink.faces - diff; i++)
413 						(void) MoveMlink(w, BOTTOM, w->mlink.currentTile, shift);
414 				else
415 					for (i = 0; i < diff; i++)
416 						(void) MoveMlink(w, TOP, w->mlink.currentTile, shift);
417 				if (!shift && CheckSolved(w)) {
418 					mlinkCallbackStruct cb;
419 
420 					cb.reason = MLINK_SOLVED;
421 					XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
422 				}
423 			}
424 		}
425 	}
426 }
427 
428 static void
RandomizeMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)429 RandomizeMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
430 {
431 	RandomizeTiles(w);
432 }
433 
434 static void
RandomizeMlinkMaybe(MlinkWidget w,XEvent * event,char ** args,int nArgs)435 RandomizeMlinkMaybe(MlinkWidget w, XEvent * event, char **args, int nArgs)
436 {
437 	if (!w->mlink.started)
438 		RandomizeTiles(w);
439 }
440 
441 static void
GetMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)442 GetMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
443 {
444 	FILE       *fp;
445 	char        c;
446 	int         i, tiles, faces, middle, orient, moves;
447 	mlinkCallbackStruct cb;
448 
449 	if ((fp = fopen(DATAFILE, "r")) == NULL)
450 		(void) printf("Can not read %s for get.\n", DATAFILE);
451 	else {
452 		FlushMoves(w);
453 		while ((c = getc(fp)) != EOF && c != SYMBOL);
454 		(void) fscanf(fp, "%d", &tiles);
455 		if (tiles >= MINTILES) {
456 			for (i = w->mlink.tiles; i < tiles; i++) {
457 				cb.reason = MLINK_INC_X;
458 				XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
459 			}
460 			for (i = w->mlink.tiles; i > tiles; i--) {
461 				cb.reason = MLINK_DEC_X;
462 				XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
463 			}
464 		} else
465 			(void) printf("%s corrupted: tiles %d should be between %d and MAXINT\n",
466 				      DATAFILE, tiles, MINTILES);
467 		while ((c = getc(fp)) != EOF && c != SYMBOL);
468 		(void) fscanf(fp, "%d", &faces);
469 		if (faces >= MINFACES && faces <= MAXFACES) {
470 			for (i = w->mlink.faces; i < faces; i++) {
471 				cb.reason = MLINK_INC_Y;
472 				XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
473 			}
474 			for (i = w->mlink.faces; i > faces; i--) {
475 				cb.reason = MLINK_DEC_Y;
476 				XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
477 			}
478 		} else
479 			(void) printf("%s corrupted: faces %d should be between %d and %d\n",
480 				      DATAFILE, faces, MINFACES, MAXFACES);
481 		while ((c = getc(fp)) != EOF && c != SYMBOL);
482 		(void) fscanf(fp, "%d", &middle);
483 		if (w->mlink.middle != (Boolean) middle) {
484 			cb.reason = MLINK_MIDDLE;
485 			XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
486 		}
487 		while ((c = getc(fp)) != EOF && c != SYMBOL);
488 		(void) fscanf(fp, "%d", &orient);
489 		if (w->mlink.orient != (Boolean) orient) {
490 			cb.reason = MLINK_ORIENT;
491 			XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
492 		}
493 		while ((c = getc(fp)) != EOF && c != SYMBOL);
494 		(void) fscanf(fp, "%d", &moves);
495 		ScanStartPosition(fp, w);
496 		cb.reason = MLINK_RESTORE;
497 		XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
498 		SetStartPosition(w);
499 		ScanMoves(fp, w, moves);
500 		(void) fclose(fp);
501 		(void) printf("%s: tiles %d, faces %d, middle %d, orient %d, moves %d.\n",
502 			      DATAFILE, tiles, faces, middle, orient, moves);
503 	}
504 }
505 
506 static void
WriteMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)507 WriteMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
508 {
509 	FILE       *fp;
510 
511 	if ((fp = fopen(DATAFILE, "w")) == NULL)
512 		(void) printf("Can not write to %s.\n", DATAFILE);
513 	else {
514 		(void) fprintf(fp, "tiles%c %d\n", SYMBOL, w->mlink.tiles);
515 		(void) fprintf(fp, "faces%c %d\n", SYMBOL, w->mlink.faces);
516 		(void) fprintf(fp, "middle%c %d\n", SYMBOL, (w->mlink.middle) ? 1 : 0);
517 		(void) fprintf(fp, "orient%c %d\n", SYMBOL, (w->mlink.orient) ? 1 : 0);
518 		(void) fprintf(fp, "moves%c %d\n", SYMBOL, NumMoves());
519 		PrintStartPosition(fp, w);
520 		PrintMoves(fp);
521 		(void) fclose(fp);
522 		(void) printf("Saved to %s.\n", DATAFILE);
523 	}
524 }
525 
526 static void
UndoMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)527 UndoMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
528 {
529 	if (MadeMoves()) {
530 		int         direction, tile, shift;
531 		mlinkCallbackStruct cb;
532 
533 		GetMove(&direction, &tile, &shift);
534 		direction = (direction + COORD / 2) % COORD;
535 		if (shift && (direction == TOP || direction == BOTTOM))
536 			MoveShiftCb(w, direction);
537 		else if (MoveTilesDir(w, direction, tile)) {
538 			cb.reason = MLINK_UNDO;
539 			XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
540 		}
541 	}
542 }
543 
544 static void
SolveMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)545 SolveMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
546 {
547 #if 0
548 	SolveTiles(w);		/* Sorry, this is not implemented */
549 #endif
550 }
551 
552 static void
OrientizeMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)553 OrientizeMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
554 {
555 	mlinkCallbackStruct cb;
556 
557 	cb.reason = MLINK_ORIENT;
558 	XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
559 }
560 
561 static void
MiddleMlink(MlinkWidget w,XEvent * event,char ** args,int nArgs)562 MiddleMlink(MlinkWidget w, XEvent * event, char **args, int nArgs)
563 {
564 	mlinkCallbackStruct cb;
565 
566 	cb.reason = MLINK_MIDDLE;
567 	XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
568 }
569 
570 static void
MoveMlinkTop(MlinkWidget w,XEvent * event,char ** args,int nArgs)571 MoveMlinkTop(MlinkWidget w, XEvent * event, char **args, int nArgs)
572 {
573 	MoveMlinkInput(w, event->xbutton.x, TOP,
574 		       (int) (event->xkey.state & (ShiftMask | LockMask)),
575 		       (int) (event->xkey.state & ControlMask));
576 }
577 
578 static void
MoveMlinkLeft(MlinkWidget w,XEvent * event,char ** args,int nArgs)579 MoveMlinkLeft(MlinkWidget w, XEvent * event, char **args, int nArgs)
580 {
581 	MoveMlinkInput(w, event->xbutton.x, LEFT,
582 		       (int) (event->xkey.state & (ShiftMask | LockMask)),
583 		       (int) (event->xkey.state & ControlMask));
584 }
585 
586 static void
MoveMlinkRight(MlinkWidget w,XEvent * event,char ** args,int nArgs)587 MoveMlinkRight(MlinkWidget w, XEvent * event, char **args, int nArgs)
588 {
589 	MoveMlinkInput(w, event->xbutton.x, RIGHT,
590 		       (int) (event->xkey.state & (ShiftMask | LockMask)),
591 		       (int) (event->xkey.state & ControlMask));
592 }
593 
594 static void
MoveMlinkBottom(MlinkWidget w,XEvent * event,char ** args,int nArgs)595 MoveMlinkBottom(MlinkWidget w, XEvent * event, char **args, int nArgs)
596 {
597 	MoveMlinkInput(w, event->xbutton.x, BOTTOM,
598 		       (int) (event->xkey.state & (ShiftMask | LockMask)),
599 		       (int) (event->xkey.state & ControlMask));
600 }
601 
602 static void
MoveMlinkInput(MlinkWidget w,int x,int direction,int shift,int control)603 MoveMlinkInput(MlinkWidget w, int x, int direction, int shift, int control)
604 {
605 	mlinkCallbackStruct cb;
606 	int         r;
607 
608 	if (control)
609 		ControlMlink(w, direction);
610 	else if (shift && (direction == TOP || direction == BOTTOM)) {
611 		MoveShiftCb(w, direction);
612 		PutMove(direction, 0, 1);
613 	} else if (CheckSolved(w)) {
614 		MoveNoTiles(w);
615 		return;
616 	} else if (direction == LEFT || direction == RIGHT) {
617 		SlideMlink(w, direction);
618 		if (CheckSolved(w)) {
619 			cb.reason = MLINK_SOLVED;
620 			XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
621 		}
622 	} else {
623 		if (!PositionTile(w, x, &r))
624 			return;
625 		RotateMlink(w, direction, r);
626 		if (CheckSolved(w)) {
627 			cb.reason = MLINK_SOLVED;
628 			XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
629 		}
630 	}
631 }
632 
633 Boolean
MoveMlink(MlinkWidget w,int direction,int tile,int shift)634 MoveMlink(MlinkWidget w, int direction, int tile, int shift)
635 {
636 	mlinkCallbackStruct cb;
637 
638 	if (shift && (direction == TOP || direction == BOTTOM)) {
639 		MoveShiftCb(w, direction);
640 		return True;
641 	} else if (CheckSolved(w)) {
642 		MoveNoTiles(w);
643 		return False;
644 	} else if (MoveTilesDir(w, direction, tile)) {
645 		cb.reason = MLINK_MOVED;
646 		XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
647 		PutMove(direction, tile, 0);
648 		return True;
649 	}
650 	return False;
651 }
652 
653 static void
ControlMlink(MlinkWidget w,int direction)654 ControlMlink(MlinkWidget w, int direction)
655 {
656 	mlinkCallbackStruct cb;
657 
658 	cb.reason = MLINK_IGNORE;
659 	switch (direction) {
660 		case TOP:
661 			if (w->mlink.faces <= MINFACES)
662 				return;
663 			cb.reason = MLINK_DEC_Y;
664 			break;
665 		case RIGHT:
666 			cb.reason = MLINK_INC_X;
667 			break;
668 		case BOTTOM:
669 			if (w->mlink.faces >= MAXFACES)
670 				return;
671 			cb.reason = MLINK_INC_Y;
672 			break;
673 		case LEFT:
674 			if (w->mlink.tiles <= MINTILES)
675 				return;
676 			cb.reason = MLINK_DEC_X;
677 			break;
678 		default:
679 			(void) printf("ControlMlink: direction %d\n", direction);
680 	}
681 	XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
682 }
683 
684 static void
RotateMlink(MlinkWidget w,int direction,int tile)685 RotateMlink(MlinkWidget w, int direction, int tile)
686 {
687 	mlinkCallbackStruct cb;
688 
689 	if (CheckSolved(w)) {
690 		MoveNoTiles(w);
691 		return;
692 	}
693 	(void) MoveMlink(w, direction, tile, FALSE);
694 	if (CheckSolved(w)) {
695 		cb.reason = MLINK_SOLVED;
696 		XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
697 	}
698 	return;
699 }
700 
701 static void
SlideMlink(MlinkWidget w,int direction)702 SlideMlink(MlinkWidget w, int direction)
703 {
704 	mlinkCallbackStruct cb;
705 
706 	if (CheckSolved(w)) {
707 		MoveNoTiles(w);
708 		return;
709 	}
710 	if (!MoveMlink(w, direction, 0, FALSE)) {
711 		cb.reason = MLINK_BLOCKED;
712 		XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
713 		return;
714 	}
715 	if (CheckSolved(w)) {
716 		cb.reason = MLINK_SOLVED;
717 		XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
718 	}
719 	return;
720 }
721 
722 static int
SelectTiles(MlinkWidget w,int x,int y,int * i,int * j)723 SelectTiles(MlinkWidget w, int x, int y, int *i, int *j)
724 {
725 	*i = (x - w->mlink.delta.x / 2 - w->mlink.puzzleOffset.x) /
726 		w->mlink.offset.x;
727 	*j = ((y - w->mlink.delta.y / 2 - w->mlink.puzzleOffset.y) %
728 	      (w->mlink.faces * w->mlink.offset.y + w->mlink.delta.y - 1)) /
729 		w->mlink.offset.y;
730 	if (*i >= 0 && *j >= 0 &&
731 	    *i < w->mlink.tiles && *j < w->mlink.faces)
732 		return (*i + w->mlink.tiles * *j -
733 			w->mlink.spacePosition % w->mlink.tileFaces);
734 	return -w->mlink.tileFaces;
735 }
736 
737 static void
SelectSlideTiles(MlinkWidget w,int pos)738 SelectSlideTiles(MlinkWidget w, int pos)
739 {
740 	mlinkCallbackStruct cb;
741 	int         n;
742 
743 	if (pos < 0) {
744 		for (n = 1; n <= -pos % w->mlink.tiles; n++) {
745 			MoveTiles(w, w->mlink.spacePosition - 1);
746 			cb.reason = MLINK_MOVED;
747 			XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
748 			PutMove(RIGHT, 0, 0);
749 		}
750 	} else if (pos > 0) {
751 		for (n = 1; n <= pos % w->mlink.tiles; n++) {
752 			MoveTiles(w, w->mlink.spacePosition + 1);
753 			cb.reason = MLINK_MOVED;
754 			XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
755 			PutMove(LEFT, 0, 0);
756 		}
757 	}
758 }
759 
760 static void
SetAllColors(MlinkWidget w,Boolean init)761 SetAllColors(MlinkWidget w, Boolean init)
762 {
763 	XGCValues   values;
764 	XtGCMask    valueMask;
765 	int         face;
766 
767 	valueMask = GCForeground | GCBackground;
768 
769 	if (w->mlink.reverse) {
770 		values.foreground = w->mlink.foreground;
771 		values.background = w->core.background_pixel;
772 	} else {
773 		values.foreground = w->core.background_pixel;
774 		values.background = w->mlink.foreground;
775 	}
776 	if (!init)
777 		XtReleaseGC((Widget) w, w->mlink.inverseGC);
778 	w->mlink.inverseGC = XtGetGC((Widget) w, valueMask, &values);
779 	if (w->mlink.reverse) {
780 		values.foreground = w->core.background_pixel;
781 		values.background = w->mlink.foreground;
782 	} else {
783 		values.foreground = w->mlink.foreground;
784 		values.background = w->core.background_pixel;
785 	}
786 	if (!init)
787 		XtReleaseGC((Widget) w, w->mlink.puzzleGC);
788 	w->mlink.puzzleGC = XtGetGC((Widget) w, valueMask, &values);
789 	if (w->mlink.depth < 2 || w->mlink.mono) {
790 		if (w->mlink.reverse) {
791 			values.background = w->mlink.foreground;
792 			values.foreground = w->core.background_pixel;
793 		} else {
794 			values.foreground = w->mlink.foreground;
795 			values.background = w->core.background_pixel;
796 		}
797 	} else {
798 		values.foreground = w->mlink.tileColor;
799 		values.background = w->mlink.borderColor;
800 	}
801 	if (!init)
802 		XtReleaseGC((Widget) w, w->mlink.tileGC);
803 	w->mlink.tileGC = XtGetGC((Widget) w, valueMask, &values);
804 	if (w->mlink.depth < 2 || w->mlink.mono) {
805 		if (w->mlink.reverse) {
806 			values.foreground = w->mlink.foreground;
807 			values.background = w->core.background_pixel;
808 		} else {
809 			values.background = w->mlink.foreground;
810 			values.foreground = w->core.background_pixel;
811 		}
812 	} else {
813 		values.foreground = w->mlink.borderColor;
814 		values.background = w->mlink.tileColor;
815 	}
816 	if (!init)
817 		XtReleaseGC((Widget) w, w->mlink.borderGC);
818 	w->mlink.borderGC = XtGetGC((Widget) w, valueMask, &values);
819 	for (face = 0; face < MAXFACES; face++)
820 		GetColor(w, face, init);
821 }
822 
823 static void
GetColor(MlinkWidget w,int face,Boolean init)824 GetColor(MlinkWidget w, int face, Boolean init)
825 {
826 	XGCValues   values;
827 	XtGCMask    valueMask;
828 	XColor      colorCell, rgb;
829 
830 	valueMask = GCForeground | GCBackground;
831 	if (w->mlink.reverse) {
832 		values.background = w->mlink.foreground;
833 	} else {
834 		values.background = w->core.background_pixel;
835 	}
836 	if (w->mlink.depth > 1 && !w->mlink.mono) {
837 		if (XAllocNamedColor(XtDisplay(w),
838 				  DefaultColormap(XtDisplay(w), XtWindow(w)),
839 				w->mlink.faceName[face], &colorCell, &rgb)) {
840 			values.foreground = w->mlink.faceColor[face] = colorCell.pixel;
841 			if (!init)
842 				XtReleaseGC((Widget) w, w->mlink.faceGC[face]);
843 			w->mlink.faceGC[face] = XtGetGC((Widget) w, valueMask, &values);
844 			return;
845 		} else {
846 			char        buf[121];
847 
848 			(void) sprintf(buf, "Color name \"%s\" is not defined",
849 				       w->mlink.faceName[face]);
850 			XtWarning(buf);
851 		}
852 	}
853 	if (w->mlink.reverse) {
854 		values.background = w->core.background_pixel;
855 		values.foreground = w->mlink.foreground;
856 	} else {
857 		values.background = w->mlink.foreground;
858 		values.foreground = w->core.background_pixel;
859 	}
860 	if (!init)
861 		XtReleaseGC((Widget) w, w->mlink.faceGC[face]);
862 	w->mlink.faceGC[face] = XtGetGC((Widget) w, valueMask, &values);
863 }
864 
865 static void
MoveShiftCb(MlinkWidget w,int direction)866 MoveShiftCb(MlinkWidget w, int direction)
867 {
868 	mlinkCallbackStruct cb;
869 
870 	if (w->mlink.middle) {
871 		(void) MoveTilesDir(w, direction, 0);
872 		(void) MoveTilesDir(w, direction, 1);
873 		(void) MoveTilesDir(w, direction, w->mlink.tiles - 1);
874 	} else {
875 		int         r;
876 
877 		for (r = 0; r < w->mlink.tiles; r++)
878 			(void) MoveTilesDir(w, direction, r);
879 	}
880 	cb.reason = MLINK_CONTROL;
881 	XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
882 }
883 
884 static void
CheckTiles(MlinkWidget w)885 CheckTiles(MlinkWidget w)
886 {
887 	char        buf[121];
888 
889 	if (w->mlink.tiles < MINTILES) {
890 		(void) sprintf(buf,
891 			       "Number of Mlink in X direction out of bounds, use %d..MAXINT",
892 			       MINTILES);
893 		XtWarning(buf);
894 		w->mlink.tiles = DEFAULTTILES;
895 	}
896 	if (w->mlink.faces < MINFACES || w->mlink.faces > MAXFACES) {
897 		(void) sprintf(buf,
898 		  "Number of Mlink in Y direction out of bounds, use %d..%d",
899 			       MINFACES, MAXFACES);
900 		XtWarning(buf);
901 		w->mlink.faces = DEFAULTFACES;
902 	}
903 	if (w->mlink.base > 36) {
904 		/* 10 numbers + 26 letters (ASCII or EBCDIC) */
905 		XtWarning("Base must be less than or equal to 36");
906 		w->mlink.base = 10;
907 	} else if (w->mlink.base <= 1) {	/* Base 1 is rediculous :) */
908 		XtWarning("Base must be greater than 1");
909 		w->mlink.base = 10;
910 	}
911 }
912 
913 static void
ResetTiles(MlinkWidget w)914 ResetTiles(MlinkWidget w)
915 {
916 	int         i;
917 
918 	w->mlink.tileFaces = w->mlink.tiles * w->mlink.faces;
919 	if (w->mlink.tileOfPosition)
920 		(void) free((void *) w->mlink.tileOfPosition);
921 	if (!(w->mlink.tileOfPosition = (int *)
922 	      malloc(sizeof (int) * w->mlink.tileFaces)))
923 		            XtError("Not enough memory, exiting.");
924 
925 	if (startPosition)
926 		(void) free((void *) startPosition);
927 	if (!(startPosition = (int *)
928 	      malloc(sizeof (int) * w->mlink.tileFaces)))
929 		            XtError("Not enough memory, exiting.");
930 
931 	w->mlink.spacePosition = w->mlink.tileFaces - 1;
932 	w->mlink.tileOfPosition[w->mlink.tileFaces - 1] = 0;
933 	for (i = 1; i < w->mlink.tileFaces; i++)
934 		w->mlink.tileOfPosition[i - 1] = i;
935 	FlushMoves(w);
936 	w->mlink.started = False;
937 }
938 
939 static void
ResizeTiles(MlinkWidget w)940 ResizeTiles(MlinkWidget w)
941 {
942 	w->mlink.digitOffset.x = 3;
943 	w->mlink.digitOffset.y = 4;
944 }
945 
946 static void
MoveNoTiles(MlinkWidget w)947 MoveNoTiles(MlinkWidget w)
948 {
949 	mlinkCallbackStruct cb;
950 
951 	cb.reason = MLINK_IGNORE;
952 	XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
953 }
954 
955 static      Boolean
MoveTilesDir(MlinkWidget w,int direction,int tile)956 MoveTilesDir(MlinkWidget w, int direction, int tile)
957 {
958 	switch (direction) {
959 		case TOP:
960 			RotateTiles(w, tile, TOP);
961 			return True;
962 		case RIGHT:
963 			if (Row(w, w->mlink.spacePosition) > 0) {
964 				MoveTiles(w, w->mlink.spacePosition - 1);
965 				return True;
966 			}
967 			break;
968 		case BOTTOM:
969 			RotateTiles(w, tile, BOTTOM);
970 			return True;
971 		case LEFT:
972 			if (Row(w, w->mlink.spacePosition) < w->mlink.tiles - 1) {
973 				MoveTiles(w, w->mlink.spacePosition + 1);
974 				return True;
975 			}
976 			break;
977 		default:
978 			(void) printf("MoveTilesDir: direction %d\n", direction);
979 	}
980 	return False;
981 }
982 
983 static void
RandomizeTiles(MlinkWidget w)984 RandomizeTiles(MlinkWidget w)
985 {
986 	mlinkCallbackStruct cb;
987 
988 	/* First interchange tiles an even number of times */
989 	if (w->mlink.tiles > 1 && w->mlink.faces > 1 && w->mlink.tileFaces > 4) {
990 		int         pos, count = 0;
991 
992 		for (pos = 0; pos < w->mlink.tileFaces; pos++) {
993 			int         randomPos = pos;
994 
995 			while (randomPos == pos)
996 				randomPos = NRAND(w->mlink.tileFaces);
997 			count += ExchangeTiles(w, pos, randomPos);
998 		}
999 		if (count % 2)
1000 			if (!ExchangeTiles(w, 0, 1))
1001 				if (!ExchangeTiles(w,
1002 						   w->mlink.tileFaces - 2, w->mlink.tileFaces - 1))
1003 					(void) printf("RandomizeTiles: should not get here\n");
1004 		DrawAllTiles(w);
1005 	}
1006 	/* Now move the space around randomly */
1007 	if (w->mlink.tiles > 1 || w->mlink.faces > 1) {
1008 		int         big = w->mlink.tileFaces + NRAND(2);
1009 		int         lastDirection = 0;
1010 		int         randomDirection;
1011 
1012 		cb.reason = MLINK_RESET;
1013 		XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
1014 
1015 #ifdef DEBUG
1016 		big = 3;
1017 #endif
1018 		if (big > 100)
1019 			big = 100;
1020 		while (big--) {
1021 			randomDirection = NRAND(COORD);
1022 
1023 #ifdef DEBUG
1024 			sleep(1);
1025 #endif
1026 
1027 			if ((randomDirection + COORD / 2) % COORD != lastDirection ||
1028 			    (w->mlink.tiles == 1 && w->mlink.faces == 1)) {
1029 				if (MoveMlink(w, randomDirection, NRAND(w->mlink.tiles), FALSE))
1030 					lastDirection = randomDirection;
1031 				else
1032 					big++;
1033 			}
1034 		}
1035 		FlushMoves(w);
1036 		cb.reason = MLINK_RANDOMIZE;
1037 		XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
1038 	}
1039 	if (CheckSolved(w)) {
1040 		cb.reason = MLINK_SOLVED;
1041 		XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
1042 	}
1043 }
1044 
1045 static void
MoveTiles(MlinkWidget w,int from)1046 MoveTiles(MlinkWidget w, int from)
1047 {
1048 	int         tempTile;
1049 
1050 	tempTile = w->mlink.tileOfPosition[from];
1051 	w->mlink.tileOfPosition[from] =
1052 		w->mlink.tileOfPosition[w->mlink.spacePosition];
1053 	w->mlink.tileOfPosition[w->mlink.spacePosition] = tempTile;
1054 	DrawTile(w, w->mlink.spacePosition, False, False, FALSE);
1055 	w->mlink.spacePosition = from;
1056 	DrawTile(w, w->mlink.spacePosition, True, True, FALSE);
1057 }
1058 
1059 static void
RotateTiles(MlinkWidget w,int c,int direction)1060 RotateTiles(MlinkWidget w, int c, int direction)
1061 {
1062 	int         newR, k, tempTile, currTile, pos, newPos;
1063 	int         r = 0, space = -1, loop = FALSE;
1064 
1065 	if (w->mlink.middle && c > 0 && c < w->mlink.tiles - 1) {
1066 		loop = TRUE;
1067 		c = 1;
1068 	}
1069 	do {
1070 		currTile = w->mlink.tileOfPosition[r * w->mlink.tiles + c];
1071 		for (k = 1; k <= w->mlink.faces; k++) {
1072 			newR = (direction == TOP) ? (r + w->mlink.faces - 1) % w->mlink.faces :
1073 				(r + 1) % w->mlink.faces;
1074 			pos = r * w->mlink.tiles + c;
1075 			newPos = newR * w->mlink.tiles + c;
1076 			tempTile = w->mlink.tileOfPosition[newPos];
1077 			w->mlink.tileOfPosition[newPos] = currTile;
1078 			currTile = tempTile;
1079 			if (w->mlink.spacePosition == pos) {
1080 				DrawTile(w, newPos, True, True, FALSE);
1081 				space = newPos;
1082 			} else
1083 				DrawTile(w, newPos, False, False, FALSE);
1084 			r = newR;
1085 		}
1086 		if (space >= 0)
1087 			w->mlink.spacePosition = space;
1088 	} while (loop && ++c < w->mlink.tiles - 1);
1089 }
1090 
1091 static int
ExchangeTiles(MlinkWidget w,int pos1,int pos2)1092 ExchangeTiles(MlinkWidget w, int pos1, int pos2)
1093 {
1094 	int         tempTile;
1095 
1096 	if (w->mlink.tileOfPosition[pos1] <= 0)
1097 		return FALSE;
1098 	else if (w->mlink.tileOfPosition[pos2] <= 0)
1099 		return FALSE;
1100 	tempTile = w->mlink.tileOfPosition[pos1];
1101 	w->mlink.tileOfPosition[pos1] = w->mlink.tileOfPosition[pos2];
1102 	w->mlink.tileOfPosition[pos2] = tempTile;
1103 	return TRUE;
1104 }
1105 
1106 static void
DrawFrame(MlinkWidget w,GC gc)1107 DrawFrame(MlinkWidget w, GC gc)
1108 {
1109 	int         sumX, sumY, offsetX, offsetY, r;
1110 
1111 	sumX = w->mlink.tiles * w->mlink.offset.x + w->mlink.delta.x / 2 + 1;
1112 	sumY = w->mlink.offset.y;
1113 	offsetX = w->mlink.puzzleOffset.x;
1114 	offsetY = w->mlink.puzzleOffset.y;
1115 	for (r = 0; r < w->mlink.faces; r++) {
1116 		XFillRectangle(XtDisplay(w), XtWindow(w), gc,
1117 			       offsetX, offsetY, 1, sumY);
1118 		XFillRectangle(XtDisplay(w), XtWindow(w), gc,
1119 			       offsetX, offsetY, sumX, 1);
1120 		XFillRectangle(XtDisplay(w), XtWindow(w), gc,
1121 			       sumX + offsetX, offsetY, 1, sumY + 1);
1122 		XFillRectangle(XtDisplay(w), XtWindow(w), gc,
1123 			       offsetX, sumY + offsetY, sumX + 1, 1);
1124 		offsetY += sumY;
1125 	}
1126 }
1127 
1128 void
DrawAllTiles(MlinkWidget w)1129 DrawAllTiles(MlinkWidget w)
1130 {
1131 	int         k;
1132 
1133 	for (k = 0; k < w->mlink.tileFaces; k++)
1134 		DrawTile(w, k, (w->mlink.tileOfPosition[k] <= 0),
1135 			 (w->mlink.tileOfPosition[k] <= 0), FALSE);
1136 }
1137 
1138 static void
DrawTile(MlinkWidget w,int pos,Boolean blank,Boolean erase,int offset)1139 DrawTile(MlinkWidget w, int pos, Boolean blank, Boolean erase, int offset)
1140 {
1141 	int         dx, dy, tile = w->mlink.tileOfPosition[pos];
1142 	GC          linkGC, tileGC, borderGC;
1143 
1144 	if (erase) {
1145 		tileGC = w->mlink.inverseGC;
1146 		borderGC = w->mlink.inverseGC;
1147 	} else if (offset) {
1148 		tileGC = w->mlink.borderGC;
1149 		borderGC = w->mlink.tileGC;
1150 	} else {
1151 		tileGC = w->mlink.tileGC;
1152 		borderGC = w->mlink.borderGC;
1153 	}
1154 	if ((w->mlink.depth < 2 || w->mlink.mono) && offset) {
1155 		linkGC = borderGC;
1156 	} else {
1157 		int         face = ((tile - 1) / w->mlink.tiles + 1) % w->mlink.faces;
1158 
1159 		linkGC = w->mlink.faceGC[face];
1160 		if (w->mlink.depth >= 2 && !w->mlink.mono) {
1161 			if (((w->mlink.faceColor[face] == w->mlink.borderColor) && offset) ||
1162 			    ((w->mlink.faceColor[face] == w->mlink.tileColor) && !offset))
1163 				linkGC = borderGC;
1164 		}
1165 	}
1166 	dx = Row(w, pos) * w->mlink.offset.x + w->mlink.delta.x +
1167 		w->mlink.puzzleOffset.x + offset;
1168 	dy = Column(w, pos) * w->mlink.offset.y + w->mlink.delta.y +
1169 		w->mlink.puzzleOffset.y + offset;
1170 	XFillRectangle(XtDisplay(w), XtWindow(w), tileGC, dx, dy,
1171 		       w->mlink.tileSize.x, w->mlink.tileSize.y - 2);
1172 	if (!blank) {
1173 		int         i = 0, offsetX = 0, t = tile;
1174 		char        buf[121];
1175 
1176 		buf[0] = '\0';
1177 		if (w->mlink.orient || w->mlink.depth < 2 || w->mlink.mono) {
1178 			if (w->mlink.depth < 2 || w->mlink.mono) {
1179 				if (w->mlink.orient) {
1180 					int         last;
1181 
1182 					last = int2String(buf, tile, w->mlink.base, False);
1183 					buf[last] = w->mlink.faceName[((tile - 1) / w->mlink.tiles + 1) %
1184 							  w->mlink.faces][0];
1185 					buf[last + 1] = '\0';
1186 					t *= w->mlink.base;
1187 				} else {
1188 					buf[0] = w->mlink.faceName[((tile - 1) /
1189 								    w->mlink.tiles + 1) % w->mlink.faces][0];
1190 					buf[1] = '\0';
1191 					t = 1;
1192 				}
1193 			} else {
1194 				(void) int2String(buf, tile, w->mlink.base, True);
1195 			}
1196 			while (t >= 1) {
1197 				t /= w->mlink.base;
1198 				offsetX += w->mlink.digitOffset.x;
1199 				i++;
1200 			}
1201 			XDrawString(XtDisplay(w), XtWindow(w), linkGC,
1202 				    dx + w->mlink.tileSize.x / 2 - offsetX,
1203 				    dy + w->mlink.tileSize.y / 2 + w->mlink.digitOffset.y, buf, i);
1204 		}
1205 	}
1206 	if (tile != 0 &&
1207 	    ((tile != w->mlink.tileFaces - 1 && w->mlink.tiles > 1) ||
1208 	     w->mlink.tiles > 2))
1209 		DrawLink(w, linkGC, pos, offset);
1210 	XDrawRectangle(XtDisplay(w), XtWindow(w), borderGC, dx, dy,
1211 		       w->mlink.tileSize.x, w->mlink.tileSize.y - 2);
1212 }
1213 
1214 #define MULT 64
1215 #define THICKNESS 7
1216 static void
DrawLink(MlinkWidget w,GC gc,int pos,int offset)1217 DrawLink(MlinkWidget w, GC gc, int pos, int offset)
1218 {
1219 	int         dx, dy, sizex, sizey, tile = w->mlink.tileOfPosition[pos],
1220 	            thick;
1221 
1222 	sizex = w->mlink.offset.x * 3 / 2 - 3;
1223 	sizey = w->mlink.offset.y * 3 / 4 - 3;
1224 	dx = Row(w, pos) * w->mlink.offset.x + w->mlink.delta.x +
1225 		w->mlink.puzzleOffset.x - sizex / 2 + offset;
1226 	dy = Column(w, pos) * w->mlink.offset.y + w->mlink.delta.y +
1227 		w->mlink.puzzleOffset.y + w->mlink.tileSize.y / 2 - sizey / 2 - 1 + offset;
1228 	thick = MIN(sizey, sizex) / 3;
1229 	thick = MIN(thick, THICKNESS);
1230 	if (thick > 1) {
1231 		XSetLineAttributes(XtDisplay(w), gc, thick,
1232 				   LineSolid, CapNotLast, JoinRound);
1233 		if (tile % w->mlink.tiles == 0 || tile == w->mlink.tileFaces - 1)
1234 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1235 				 sizex, sizey, 89 * MULT, -179 * MULT);
1236 		else if (tile % w->mlink.tiles == 1)
1237 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx + w->mlink.tileSize.x - 1, dy,
1238 				 sizex, sizey, 90 * MULT, 180 * MULT);
1239 		else {
1240 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1241 				 sizex, sizey, 89 * MULT, -124 * MULT);
1242 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1243 				 sizex, sizey, -90 * MULT, 30 * MULT);
1244 			dx += w->mlink.tileSize.x - 1;
1245 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1246 				 sizex, sizey, 90 * MULT, 30 * MULT);
1247 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1248 				 sizex, sizey, -90 * MULT, -125 * MULT);
1249 		}
1250 		XSetLineAttributes(XtDisplay(w), gc, 1, LineSolid, CapNotLast, JoinRound);
1251 	}
1252 }
1253 
1254 #if 0
1255 #define MULT 64
1256 #define THICKNESS 8
1257 static void
1258 DrawLink(w, gc, pos, offset)
1259 	MlinkWidget w;
1260 	GC          gc;
1261 	int         pos;
1262 {
1263 	int         dx, dy, sizex, sizey, i, tile = w->mlink.tileOfPosition[pos];
1264 
1265 	for (i = 0; i < THICKNESS; i++) {
1266 		sizex = w->mlink.offset.x * 3 / 2 - i;
1267 		sizey = w->mlink.offset.y * 3 / 4 - i;
1268 		dx = Row(w, pos) * w->mlink.offset.x + w->mlink.delta.x +
1269 			w->mlink.puzzleOffset.x - sizex / 2 + offset;
1270 		dy = Column(w, pos) * w->mlink.offset.y + w->mlink.delta.y +
1271 			w->mlink.puzzleOffset.y + w->mlink.tileSize.y / 2 - sizey / 2 + offset;
1272 		if (tile % w->mlink.tiles == 0 || tile == w->mlink.tileFaces - 1)
1273 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1274 				 sizex, sizey, 89 * MULT, -179 * MULT);
1275 		else if (tile % w->mlink.tiles == 1)
1276 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx + w->mlink.tileSize.x - 1, dy,
1277 				 sizex, sizey, 90 * MULT, 180 * MULT);
1278 		else {
1279 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1280 				 sizex, sizey, 89 * MULT, -32 * MULT);
1281 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1282 				 sizex, sizey, -90 * MULT, 128 * MULT);
1283 			dx += w->mlink.tileSize.x - 1;
1284 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1285 				 sizex, sizey, 90 * MULT, 128 * MULT);
1286 			XDrawArc(XtDisplay(w), XtWindow(w), gc, dx, dy,
1287 				 sizex, sizey, -90 * MULT, -33 * MULT);
1288 		}
1289 	}
1290 }
1291 #endif
1292 
1293 static      Boolean
PositionTile(MlinkWidget w,int x,int * r)1294 PositionTile(MlinkWidget w, int x, int *r)
1295 {
1296 	*r = (x - w->mlink.delta.x / 2 - w->mlink.puzzleOffset.x) / w->mlink.offset.x;
1297 	if (*r < 0 || *r >= w->mlink.tiles)
1298 		return False;
1299 	return True;
1300 }
1301 
1302 static int
Row(MlinkWidget w,int pos)1303 Row(MlinkWidget w, int pos)
1304 {
1305 	return (pos % w->mlink.tiles);
1306 }
1307 
1308 static int
Column(MlinkWidget w,int pos)1309 Column(MlinkWidget w, int pos)
1310 {
1311 	return (pos / w->mlink.tiles);
1312 }
1313 
1314 static int
int2String(char * buf,int number,int base,Boolean capital)1315 int2String(char *buf, int number, int base, Boolean capital)
1316 {
1317 	int         digit, mult = base, last, position;
1318 	int         a, i, j, s, r;
1319 
1320 	if (capital) {
1321 		a = 'A', i = 'I', j = 'J', s = 'S', r = 'R';
1322 	} else {
1323 		a = 'a', i = 'i', j = 'j', s = 's', r = 'r';
1324 	}
1325 	if (number < 0) {
1326 		(void) printf("number %d < 0\n", number);
1327 		return 0;
1328 	}
1329 	last = 1;
1330 	while (number >= mult) {
1331 		last++;
1332 		mult *= base;
1333 	}
1334 	for (position = 0; position < last; position++) {
1335 		mult /= base;
1336 		digit = number / mult;
1337 		number -= digit * mult;
1338 		buf[position] = digit + '0';
1339 		if (buf[position] > '9') {	/* ASCII */
1340 			buf[position] += (a - '9' - 1);
1341 		} else if (buf[position] < '0') {	/* EBCDIC */
1342 			buf[position] += (a - '9' - 1);
1343 			if (buf[position] > i)
1344 				buf[position] += (j - i - 1);
1345 			if (buf[position] > r)
1346 				buf[position] += (s - r - 1);
1347 		}
1348 	}
1349 	buf[last] = '\0';
1350 	return last;
1351 }
1352 
1353 Boolean
CheckSolved(MlinkWidget w)1354 CheckSolved(MlinkWidget w)
1355 {
1356 	int         i, j, firstTile, lastTile, midTile;
1357 
1358 	if (w->mlink.tiles < 2)
1359 		return True;
1360 	if (w->mlink.orient) {
1361 		firstTile = w->mlink.tileOfPosition[0];
1362 		if (firstTile != 0 && (firstTile - 1) % w->mlink.tiles == 0) {
1363 			for (i = 1; i < w->mlink.tileFaces; i++) {
1364 				midTile = w->mlink.tileOfPosition[i];
1365 				if (midTile && (midTile - firstTile + w->mlink.tileFaces) %
1366 				    w->mlink.tileFaces != i)
1367 					return False;
1368 			}
1369 		} else
1370 			return False;
1371 	} else {
1372 		for (i = 0; i < w->mlink.faces; i++) {
1373 			firstTile = w->mlink.tileOfPosition[i * w->mlink.tiles];
1374 			if (firstTile == 0)
1375 				firstTile = w->mlink.tileOfPosition[i * w->mlink.tiles + 1];
1376 			lastTile = w->mlink.tileOfPosition[(i + 1) * w->mlink.tiles - 1];
1377 			if (lastTile == 0)
1378 				lastTile = w->mlink.tileOfPosition[(i + 1) * w->mlink.tiles - 2];
1379 			if (firstTile % w->mlink.tiles != 1 ||
1380 			    (lastTile % w->mlink.tiles != 0 &&
1381 			     lastTile != w->mlink.tileFaces - 1) ||
1382 			    (lastTile - 1) / w->mlink.tiles !=
1383 			    (firstTile - 1) / w->mlink.tiles)
1384 				return False;
1385 			for (j = 1; j < w->mlink.tiles - 1; j++) {
1386 				midTile = w->mlink.tileOfPosition[i * w->mlink.tiles + j];
1387 				if ((midTile - 1) / w->mlink.tiles !=
1388 				    (firstTile - 1) / w->mlink.tiles || midTile == 0)
1389 					return False;
1390 			}
1391 		}
1392 	}
1393 	return True;
1394 }
1395