xenocara/app/xlockmore/modes/puzzle.c
2006-11-26 11:07:42 +00:00

671 lines
17 KiB
C

/* -*- Mode: C; tab-width: 4 -*- */
/* puzzle --- the familiar Sam Loyd puzzle */
#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)puzzle.c 5.00 2000/11/01 xlockmore";
#endif
/*-
* Copyright (c) 1995 by Heath Rice <rice@asl.dl.nec.com>.
*
* 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.
*
* This file is provided AS IS with no warranties of any kind. The author
* shall have no liability with respect to the infringement of copyrights,
* trade secrets or any patents by this file or any part thereof. In no
* event will the author be liable for any lost revenue or profits or
* other special, indirect and consequential damages.
*
* Revision History:
* 01-Nov-2000: Allocation checks
* 20-Mar-1998: Ideas from slip.c and eyes.c, problems with XGetImage when
* image moved off screen (-inwindow or -debug).
* 10-May-1997: Compatible with xscreensaver
* 15-Mar-1996: cleaned up, NUMBERED compile-time switch is now broken.
* Feb-1996: combined with rastering. Jouk Jansen <joukj@hrem.stm.tudelft.nl>.
* Feb-1995: written. Heath Rice <hrice@netcom.com>
*/
/*-
* Chops up the screen into squares and randomly slides them around
* like that game where you try to rearrange the little tiles in their
* original order. After it scrambles the tiles for awhile, it reverses
* itself and puts them back like they started. This mode looks the coolest
* if you have a picture on your background.
*/
#ifdef STANDALONE
#define MODE_puzzle
#define PROGCLASS "Puzzle"
#define HACK_INIT init_puzzle
#define HACK_DRAW draw_puzzle
#define puzzle_opts xlockmore_opts
#define DEFAULTS "*delay: 10000 \n" \
"*count: 250 \n" \
"*ncolors: 64 \n" \
"*bitmap: \n"
#include "xlockmore.h" /* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h" /* in xlockmore distribution */
#include "color.h"
#endif /* STANDALONE */
#include "iostuff.h"
#ifdef MODE_puzzle
ModeSpecOpt puzzle_opts =
{0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};
#ifdef USE_MODULES
ModStruct puzzle_description =
{"puzzle", "init_puzzle", "draw_puzzle", "release_puzzle",
"init_puzzle", "init_puzzle", (char *) NULL, &puzzle_opts,
10000, 250, 1, 1, 64, 1.0, "",
"Shows a puzzle being scrambled and then solved", 0, NULL};
#endif
#define PUZZLE_WIDTH image_width
#define PUZZLE_HEIGHT image_height
#define PUZZLE_BITS image_bits
#include "puzzle.xbm"
#ifdef HAVE_XPM
#define PUZZLE_NAME image_name
#include "puzzle.xpm"
#define DEFAULT_XPM 1
#endif
#define NOWAY 255
/*int storedmoves, *moves, *position, space; To keep track of puzzle */
typedef struct {
Bool painted; /* For debouncing */
int excount;
int *fixbuff;
XPoint count, boxsize, windowsize;
XPoint usablewindow, offsetwindow;
XPoint randompos, randpos;
unsigned long black;
int row, col, nextrow, nextcol, nextbox;
int incrementOfMove;
int lastbox;
int forward;
int prev;
int moves;
/* Delta move stuff */
int movingBox;
Pixmap bufferBox;
int cfactor, rfactor;
int Lp;
int cbs, cbw, rbs, rbw;
int lengthOfMove;
#ifdef NUMBERED
XImage *image;
GC gc;
/* Font stuff */
int done;
int ascent, fontWidth, fontHeight;
#else
XImage *logo;
GC backGC;
Colormap cmap;
int graphics_format;
#endif
} puzzlestruct;
static puzzlestruct *puzzles = (puzzlestruct *) NULL;
static void
free_stuff(Display * display, puzzlestruct * pp)
{
if (pp->cmap != None) {
XFreeColormap(display, pp->cmap);
if (pp->backGC != None) {
XFreeGC(display, pp->backGC);
pp->backGC = None;
}
pp->cmap = None;
} else
pp->backGC = None;
}
static void
free_puzzle(Display * display, puzzlestruct * pp)
{
if (pp->fixbuff != NULL) {
free(pp->fixbuff);
pp->fixbuff = (int *) NULL;
}
if (pp->bufferBox != None) {
XFreePixmap(display, pp->bufferBox);
pp->bufferBox = None;
}
free_stuff(display, pp);
if (pp->logo != None) {
destroyImage(&pp->logo, &pp->graphics_format);
pp->logo = None;
}
}
#ifdef NUMBERED
extern XFontStruct *getFont(Display * display);
#define font_height(f) (f->ascent + f->descent)
static XFontStruct *mode_font = None;
static int
font_width(XFontStruct * font, char ch)
{
int dummy;
XCharStruct xcs;
(void) XTextExtents(font, &ch, 1, &dummy, &dummy, &dummy, &xcs);
return xcs.width;
}
static Bool
NumberScreen(ModeInfo * mi)
{
Display *display = MI_DISPLAY(mi);
Window window = MI_WINDOW(mi);
puzzlestruct *pp = &puzzles[MI_SCREEN(mi)];
if (mode_font == None)
mode_font = getFont(display);
if (!pp->done) {
XGCValues gcv;
pp->done = 1;
gcv.font = mode_font->fid;
XSetFont(display, MI_GC(mi), mode_font->fid);
gcv.graphics_exposures = False;
gcv.foreground = MI_WHITE_PIXEL(mi);
gcv.background = MI_BLACK_PIXEL(mi);
if ((pp->gc = XCreateGC(display, window,
GCForeground | GCBackground | GCGraphicsExposures | GCFont,
&gcv)) == None) {
free_puzzle(display, pp);
return False;
}
pp->ascent = mode_font->ascent;
pp->fontHeight = font_height(mode_font);
pp->fontWidth = font_width(mode_font, '5');
}
XSetForeground(display, pp->gc, MI_WHITE_PIXEL(mi));
{
XPoint pos, letter;
int count = 1, digitOffset = 1, temp, letterOffset;
int i, j, mult = pp->count.x * pp->count.y;
char buf[16];
letter.x = pp->boxsize.x / 2 - 3;
letter.y = pp->boxsize.y / 2 + pp->ascent / 2 - 1;
letterOffset = pp->fontWidth / 2;
pos.y = 0;
for (j = 0; j < pp->count.y; j++) {
pos.x = 0;
for (i = 0; i < pp->count.x; i++) {
if (count < mult) {
if (pp->boxsize.x > 2 * pp->fontWidth &&
pp->boxsize.y > pp->fontHeight) {
(void) sprintf(buf, "%d", count);
(void) XDrawString(display, window, pp->gc,
pos.x + letter.x - letterOffset * digitOffset +
pp->randompos.x,
pos.y + letter.y + pp->randompos.y, buf, digitOffset);
}
XDrawRectangle(display, window, pp->gc,
pos.x + 1 + pp->randompos.x, pos.y + 1 + pp->randompos.y,
pp->boxsize.x - 3, pp->boxsize.y - 3);
count++;
digitOffset = 0;
temp = count;
while (temp >= 1) {
temp /= 10;
digitOffset++;
}
}
pos.x += pp->boxsize.x;
}
pos.y += pp->boxsize.y;
}
}
return True;
}
#endif
static int
setupmove(ModeInfo * mi)
{
puzzlestruct *pp = &puzzles[MI_SCREEN(mi)];
if ((pp->prev == pp->excount) && (pp->excount > 0) && (pp->forward == 1)) {
pp->lastbox = -1;
pp->forward = 0;
pp->prev--;
} else if ((pp->prev == -1) && (pp->excount > 0) && (pp->forward == 0)) {
pp->lastbox = -1;
pp->forward = 1;
pp->prev++;
}
if (pp->forward)
pp->nextbox = NRAND(5);
else
pp->nextbox = pp->fixbuff[pp->prev];
switch (pp->nextbox) {
case 0:
if ((pp->row == 0) || (pp->lastbox == 2))
pp->nextbox = NOWAY;
else {
pp->nextrow = pp->row - 1;
pp->nextcol = pp->col;
}
break;
case 1:
if ((pp->col == pp->count.x - 1) || (pp->lastbox == 3))
pp->nextbox = NOWAY;
else {
pp->nextrow = pp->row;
pp->nextcol = pp->col + 1;
}
break;
case 2:
if ((pp->row == pp->count.y - 1) || (pp->lastbox == 0))
pp->nextbox = NOWAY;
else {
pp->nextrow = pp->row + 1;
pp->nextcol = pp->col;
}
break;
case 3:
if ((pp->col == 0) || (pp->lastbox == 1))
pp->nextbox = NOWAY;
else {
pp->nextrow = pp->row;
pp->nextcol = pp->col - 1;
}
break;
default:
pp->nextbox = NOWAY;
break;
}
if (pp->nextbox != NOWAY) {
pp->lastbox = pp->nextbox;
return True;
} else
return False;
}
static void
setupmovedelta(ModeInfo * mi)
{
Display *display = MI_DISPLAY(mi);
puzzlestruct *pp = &puzzles[MI_SCREEN(mi)];
if (pp->bufferBox != None) {
XFreePixmap(display, pp->bufferBox);
pp->bufferBox = None;
}
if ((pp->bufferBox = XCreatePixmap(display, MI_WINDOW(mi),
pp->boxsize.x, pp->boxsize.y, MI_DEPTH(mi))) == None) {
free_puzzle(display, pp);
return;
}
XCopyArea(MI_DISPLAY(mi), MI_WINDOW(mi), pp->bufferBox, pp->backGC,
pp->nextcol * pp->boxsize.x + pp->randompos.x + 1,
pp->nextrow * pp->boxsize.y + pp->randompos.y + 1,
pp->boxsize.x - 2, pp->boxsize.y - 2, 0, 0);
XFlush(MI_DISPLAY(mi));
if (pp->nextcol > pp->col) {
pp->cfactor = -1;
pp->cbs = pp->boxsize.x;
pp->cbw = pp->incrementOfMove;
} else if (pp->col > pp->nextcol) {
pp->cfactor = 1;
pp->cbs = -pp->incrementOfMove;
pp->cbw = pp->incrementOfMove;
} else {
pp->cfactor = 0;
pp->cbs = 0;
pp->cbw = pp->boxsize.x;
}
if (pp->nextrow > pp->row) {
pp->rfactor = -1;
pp->rbs = pp->boxsize.y;
pp->rbw = pp->incrementOfMove;
} else if (pp->row > pp->nextrow) {
pp->rfactor = 1;
pp->rbs = -pp->incrementOfMove;
pp->rbw = pp->incrementOfMove;
} else {
pp->rfactor = 0;
pp->rbs = 0;
pp->rbw = pp->boxsize.y;
}
if (pp->cfactor == 0)
pp->lengthOfMove = pp->boxsize.y;
else if (pp->rfactor == 0)
pp->lengthOfMove = pp->boxsize.x;
else
pp->lengthOfMove = MIN(pp->boxsize.x, pp->boxsize.y);
pp->Lp = pp->incrementOfMove;
}
static void
wrapupmove(ModeInfo * mi)
{
puzzlestruct *pp = &puzzles[MI_SCREEN(mi)];
if (pp->excount) {
if (pp->forward) {
pp->fixbuff[pp->prev] = (pp->nextbox + 2) % 4;
pp->prev++;
} else
pp->prev--;
}
}
static void
wrapupmovedelta(ModeInfo * mi)
{
puzzlestruct *pp = &puzzles[MI_SCREEN(mi)];
if (pp->bufferBox) {
XCopyArea(MI_DISPLAY(mi), pp->bufferBox, MI_WINDOW(mi), pp->backGC,
0, 0, pp->boxsize.x - 2, pp->boxsize.y - 2,
pp->col * pp->boxsize.x + pp->randompos.x + 1,
pp->row * pp->boxsize.y + pp->randompos.y + 1);
XFlush(MI_DISPLAY(mi));
pp->row = pp->nextrow;
pp->col = pp->nextcol;
XFreePixmap(MI_DISPLAY(mi), pp->bufferBox);
pp->bufferBox = None;
}
}
static int
moveboxdelta(ModeInfo * mi)
{
puzzlestruct *pp = &puzzles[MI_SCREEN(mi)];
int cf = pp->nextcol * pp->boxsize.x +
pp->Lp * pp->cfactor + pp->randompos.x;
int rf = pp->nextrow * pp->boxsize.y +
pp->Lp * pp->rfactor + pp->randompos.y;
if (pp->Lp <= pp->lengthOfMove) {
if (pp->bufferBox) {
XCopyArea(MI_DISPLAY(mi), pp->bufferBox, MI_WINDOW(mi),
pp->backGC, 0, 0, pp->boxsize.x - 2, pp->boxsize.y - 2,
cf + 1, rf + 1);
XFillRectangle(MI_DISPLAY(mi), MI_WINDOW(mi), pp->backGC,
cf + pp->cbs - 1, rf + pp->rbs - 1, pp->cbw + 2, pp->rbw + 2);
}
if ((pp->Lp + pp->incrementOfMove > pp->lengthOfMove) &&
(pp->Lp != pp->lengthOfMove))
pp->Lp = pp->lengthOfMove - pp->incrementOfMove;
pp->Lp += pp->incrementOfMove;
return False;
} else
return True;
}
static Bool
init_stuff(ModeInfo * mi)
{
Display *display = MI_DISPLAY(mi);
Window window = MI_WINDOW(mi);
puzzlestruct *pp = &puzzles[MI_SCREEN(mi)];
if (pp->logo == None)
getImage(mi, &pp->logo, PUZZLE_WIDTH, PUZZLE_HEIGHT, PUZZLE_BITS,
#ifdef HAVE_XPM
DEFAULT_XPM, PUZZLE_NAME,
#endif
&pp->graphics_format, &pp->cmap, &pp->black);
if (pp->logo == None) {
free_puzzle(display, pp);
return False;
}
#ifndef STANDALONE
if (pp->cmap != None) {
setColormap(display, window, pp->cmap, MI_IS_INWINDOW(mi));
if (pp->backGC == None) {
XGCValues xgcv;
xgcv.background = pp->black;
if ((pp->backGC = XCreateGC(display, window, GCBackground,
&xgcv)) == None) {
free_puzzle(display, pp);
return False;
}
}
} else
#endif /* STANDALONE */
{
pp->black = MI_BLACK_PIXEL(mi);
pp->backGC = MI_GC(mi);
}
return True;
}
void
init_puzzle(ModeInfo * mi)
{
Display *display = MI_DISPLAY(mi);
Window window = MI_WINDOW(mi);
puzzlestruct *pp;
int x, y;
XPoint size;
if (puzzles == NULL) {
if ((puzzles = (puzzlestruct *) calloc(MI_NUM_SCREENS(mi),
sizeof (puzzlestruct))) == NULL)
return;
}
pp = &puzzles[MI_SCREEN(mi)];
if (pp->painted && pp->windowsize.x == MI_WIDTH(mi) &&
pp->windowsize.y == MI_HEIGHT(mi))
return; /* Debounce since refresh_puzzle is init_puzzle */
#ifdef HAVE_XPM
if (pp->graphics_format >= IS_XPM) {
/* This is needed when another program changes the colormap. */
free_puzzle(display, pp);
}
#endif
if (!init_stuff(mi))
return;
pp->excount = MI_COUNT(mi);
if (pp->excount < 0) {
if (pp->fixbuff != NULL) {
free(pp->fixbuff);
pp->fixbuff = (int *) NULL;
}
pp->excount = NRAND(-pp->excount) + 1;
}
pp->lastbox = -1;
pp->moves = 0;
pp->movingBox = False;
pp->windowsize.x = MI_WIDTH(mi);
pp->windowsize.y = MI_HEIGHT(mi);
if (pp->windowsize.x < 7)
pp->windowsize.x = 7;
if (pp->windowsize.y < 7)
pp->windowsize.y = 7;
pp->forward = 1;
pp->prev = 0;
/* don't want any exposure events from XCopyArea */
XSetGraphicsExposures(display, pp->backGC, False);
MI_CLEARWINDOWCOLORMAP(mi, pp->backGC, pp->black);
if (pp->logo) {
size.x = (pp->logo->width < pp->windowsize.x) ?
pp->logo->width : pp->windowsize.x;
size.y = (pp->logo->height < pp->windowsize.y) ?
pp->logo->height : pp->windowsize.y;
} else {
size.x = pp->windowsize.x;
size.y = pp->windowsize.y;
}
pp->boxsize.y = NRAND(1 + size.y / 4) + 6;
pp->boxsize.x = NRAND(1 + size.x / 4) + 6;
if ((pp->boxsize.x > 4 * pp->boxsize.y) ||
pp->boxsize.y > 4 * pp->boxsize.x)
pp->boxsize.x = pp->boxsize.y = 2 * MIN(pp->boxsize.x, pp->boxsize.y);
pp->count.x = size.x / pp->boxsize.x;
pp->count.y = size.y / pp->boxsize.y;
if (pp->bufferBox != None) {
XFreePixmap(display, pp->bufferBox);
pp->bufferBox = None;
}
pp->usablewindow.x = pp->count.x * pp->boxsize.x;
pp->usablewindow.y = pp->count.y * pp->boxsize.y;
pp->offsetwindow.x = (pp->windowsize.x - pp->usablewindow.x) / 2;
pp->offsetwindow.y = (pp->windowsize.y - pp->usablewindow.y) / 2;
pp->incrementOfMove = MIN(pp->usablewindow.x, pp->usablewindow.y) / 20;
pp->incrementOfMove = MAX(pp->incrementOfMove, 1);
if (pp->logo) {
pp->randompos.x = NRAND(MAX((pp->windowsize.x - pp->logo->width),
2 * pp->offsetwindow.x + 1));
pp->randompos.y = NRAND(MAX((pp->windowsize.y - pp->logo->height),
2 * pp->offsetwindow.y + 1));
if (MI_NPIXELS(mi) <= 2)
XSetForeground(display, pp->backGC, MI_WHITE_PIXEL(mi));
else
XSetForeground(display, pp->backGC, MI_PIXEL(mi, NRAND(MI_NPIXELS(mi))));
(void) XPutImage(display, window, pp->backGC, pp->logo,
(int) (NRAND(MAX(1, (pp->logo->width - pp->usablewindow.x)))),
(int) (NRAND(MAX(1, (pp->logo->height - pp->usablewindow.y)))),
pp->randompos.x, pp->randompos.y,
pp->usablewindow.x, pp->usablewindow.y);
XSetForeground(display, pp->backGC, pp->black);
for (x = 0; x <= pp->count.x; x++) {
int tempx = x * pp->boxsize.x;
XDrawLine(display, window, pp->backGC,
tempx + pp->randompos.x, pp->randompos.y,
tempx + pp->randompos.x, pp->usablewindow.y + pp->randompos.y);
XDrawLine(display, window, pp->backGC,
tempx + pp->randompos.x - 1, pp->randompos.y,
tempx + pp->randompos.x - 1, pp->usablewindow.y + pp->randompos.y);
}
for (y = 0; y <= pp->count.y; y++) {
int tempy = y * pp->boxsize.y;
XDrawLine(display, window, pp->backGC,
pp->randompos.x, tempy + pp->randompos.y,
pp->usablewindow.x + pp->randompos.x, tempy + pp->randompos.y);
XDrawLine(display, window, pp->backGC,
pp->randompos.x, tempy + pp->randompos.y - 1,
pp->usablewindow.x + pp->randompos.x, tempy + pp->randompos.y - 1);
}
}
#ifdef NUMBERED
else {
if (pp->image)
(void) XDestroyImage(pp->image);
pp->randompos.x = pp->offsetwindow.x;
pp->randompos.y = pp->offsetwindow.y;
if (!NumberScreen(mi)) {
release_puzzles(mi);
return;
}
if ((pp->image = XGetImage(display, window,
pp->offsetwindow.x, pp->offsetwindow.y,
pp->usablewindow.x, pp->usablewindow.y,
AllPlanes,
(MI_NPIXELS(mi) <= 2) ? XYPixmap : ZPixmap)) == None) {
free_puzzle(display, pp);
return;
}
}
pp->row = pp->count.y - 1;
pp->col = pp->count.x - 1;
#else
pp->row = NRAND(pp->count.y);
pp->col = NRAND(pp->count.x);
#endif
if ((pp->excount) && (pp->fixbuff == NULL))
if ((pp->fixbuff = (int *) calloc(pp->excount,
sizeof (int))) == NULL) {
free_puzzle(display, pp);
return;
}
pp->painted = True;
}
void
draw_puzzle(ModeInfo * mi)
{
puzzlestruct *pp;
if (puzzles == NULL)
return;
pp = &puzzles[MI_SCREEN(mi)];
if (pp->fixbuff == NULL)
return;
MI_IS_DRAWN(mi) = True;
pp->painted = False;
if (pp->movingBox) {
if (moveboxdelta(mi)) {
wrapupmovedelta(mi);
wrapupmove(mi);
pp->movingBox = False;
if (pp->moves++ > 2 * MI_COUNT(mi))
init_puzzle(mi);
}
} else {
if (setupmove(mi)) {
setupmovedelta(mi);
pp->movingBox = True;
}
}
}
void
release_puzzle(ModeInfo * mi)
{
if (puzzles != NULL) {
int screen;
for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
free_puzzle(MI_DISPLAY(mi), &puzzles[screen]);
free(puzzles);
puzzles = (puzzlestruct *) NULL;
}
#ifdef NUMBERED
if (mode_font != None) {
XFreeFont(MI_DISPLAY(mi), mode_font);
mode_font = None;
}
#endif
}
#endif /* MODE_puzzle */