X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fjigsaw.c;h=dce647bcc4e21307b67c7133b13dfd6efe42cd5e;hp=9d7506db5de793d749d7c95cc3c93928127d6e53;hb=49f5b54f312fe4ac2e9bc47581a72451bd0e8439;hpb=ccb7f4903325f92555a9722bba74b58346654ba0 diff --git a/hacks/jigsaw.c b/hacks/jigsaw.c index 9d7506db..dce647bc 100644 --- a/hacks/jigsaw.c +++ b/hacks/jigsaw.c @@ -1,5 +1,4 @@ -/* xscreensaver, Copyright (c) 1997, 1998, 2001, 2003, 2005 - * Jamie Zawinski +/* xscreensaver, Copyright (c) 1997-2006 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -22,9 +21,6 @@ = Rotate the pieces as well, so that we can swap the corner and edge pieces with each other. - = The shapes of the piece bitmaps still aren't quite right. - They should line up with no overlap. They don't... - = Have it drop all pieces to the "floor" then pick them up to reassemble the picture. @@ -33,51 +29,10 @@ */ #include "screenhack.h" +#include "spline.h" -#define DEBUG - -#include "images/jigsaw/jigsaw_a_h.xbm" -#include "images/jigsaw/jigsaw_a_n_h.xbm" -#include "images/jigsaw/jigsaw_a_ne_h.xbm" -#include "images/jigsaw/jigsaw_a_e_h.xbm" -#include "images/jigsaw/jigsaw_a_se_h.xbm" -#include "images/jigsaw/jigsaw_a_s_h.xbm" -#include "images/jigsaw/jigsaw_a_sw_h.xbm" -#include "images/jigsaw/jigsaw_a_w_h.xbm" -#include "images/jigsaw/jigsaw_a_nw_h.xbm" - -#include "images/jigsaw/jigsaw_b_h.xbm" -#include "images/jigsaw/jigsaw_b_n_h.xbm" -#include "images/jigsaw/jigsaw_b_ne_h.xbm" -#include "images/jigsaw/jigsaw_b_e_h.xbm" -#include "images/jigsaw/jigsaw_b_se_h.xbm" -#include "images/jigsaw/jigsaw_b_s_h.xbm" -#include "images/jigsaw/jigsaw_b_sw_h.xbm" -#include "images/jigsaw/jigsaw_b_w_h.xbm" -#include "images/jigsaw/jigsaw_b_nw_h.xbm" - -#include "images/jigsaw/jigsaw_a_f.xbm" -#include "images/jigsaw/jigsaw_a_n_f.xbm" -#include "images/jigsaw/jigsaw_a_ne_f.xbm" -#include "images/jigsaw/jigsaw_a_e_f.xbm" -#include "images/jigsaw/jigsaw_a_se_f.xbm" -#include "images/jigsaw/jigsaw_a_s_f.xbm" -#include "images/jigsaw/jigsaw_a_sw_f.xbm" -#include "images/jigsaw/jigsaw_a_w_f.xbm" -#include "images/jigsaw/jigsaw_a_nw_f.xbm" - -#include "images/jigsaw/jigsaw_b_f.xbm" -#include "images/jigsaw/jigsaw_b_n_f.xbm" -#include "images/jigsaw/jigsaw_b_ne_f.xbm" -#include "images/jigsaw/jigsaw_b_e_f.xbm" -#include "images/jigsaw/jigsaw_b_se_f.xbm" -#include "images/jigsaw/jigsaw_b_s_f.xbm" -#include "images/jigsaw/jigsaw_b_sw_f.xbm" -#include "images/jigsaw/jigsaw_b_w_f.xbm" -#include "images/jigsaw/jigsaw_b_nw_f.xbm" - -#define GRID_WIDTH 66 -#define GRID_HEIGHT 66 +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) #define CENTER 0 #define NORTH 1 @@ -104,484 +59,835 @@ struct set { #define PIECE_B_HOLLOW 2 #define PIECE_B_FILLED 3 -static struct set all_pieces[4]; +struct swap_state { + int flashing; + int x1, y1, x2, y2; + Bool draw_p; +}; + +struct state { + Display *dpy; + Window window; + + struct set all_pieces[4]; + + int piece_width, piece_height; + int width, height; + int x_border, y_border; + Pixmap source; + GC gc; + int fg, bg; + int border_width; + XPoint *state; + int delay, delay2; + + int jigstate; + struct swap_state swap; + int clearing; + + async_load_state *img_loader; +}; + + +/* Returns a spline describing one edge of a puzzle piece of the given length. + */ +static spline * +make_puzzle_curve (int pixels) +{ + double x0 = 0.0000, y0 = 0.0000; + double x1 = 0.3333, y1 = 0.1000; + double x2 = 0.4333, y2 = 0.0333; + double x3 = 0.4666, y3 = -0.0666; + double x4 = 0.3333, y4 = -0.1666; + double x5 = 0.3666, y5 = -0.2900; + double x6 = 0.5000, y6 = -0.3333; + + spline *s = make_spline(20); + s->n_controls = 0; + +# define PT(x,y) \ + s->control_x[s->n_controls] = pixels * (x); \ + s->control_y[s->n_controls] = pixels * (y); \ + s->n_controls++ + PT ( x0, y0); + PT ( x1, y1); + PT ( x2, y2); + PT ( x3, y3); + PT ( x4, y4); + PT ( x5, y5); + PT ( x6, y6); + PT (1-x5, y5); + PT (1-x4, y4); + PT (1-x3, y3); + PT (1-x2, y2); + PT (1-x1, y1); + PT (1-x0, y0); +# undef PT + + compute_spline (s); + return s; +} + + +/* Draws a puzzle piece. The top/right/bottom/left_type args + indicate the direction the tabs point: 1 for out, -1 for in, 0 for flat. + */ static void -init_images(Display *dpy, Window window) +draw_puzzle_shape (Display *dpy, Drawable d, GC gc, + int x, int y, int size, int bw, + int top_type, int right_type, + int bottom_type, int left_type, + Bool fill_p) { -# define LOAD_PIECE(PIECE,NAME) \ - PIECE.x = jigsaw_##NAME##_x_hot; \ - PIECE.y = jigsaw_##NAME##_y_hot; \ - PIECE.pixmap = \ - XCreatePixmapFromBitmapData(dpy, window, \ - (char *) jigsaw_##NAME##_bits, \ - jigsaw_##NAME##_width, \ - jigsaw_##NAME##_height, \ - 1, 0, 1) - -# define LOAD_PIECES(SET,PREFIX,SUFFIX) \ - LOAD_PIECE(SET.pieces[CENTER], PREFIX##_##SUFFIX); \ - LOAD_PIECE(SET.pieces[NORTH], PREFIX##_n_##SUFFIX); \ - LOAD_PIECE(SET.pieces[NORTHEAST], PREFIX##_ne_##SUFFIX); \ - LOAD_PIECE(SET.pieces[EAST], PREFIX##_e_##SUFFIX); \ - LOAD_PIECE(SET.pieces[SOUTHEAST], PREFIX##_se_##SUFFIX); \ - LOAD_PIECE(SET.pieces[SOUTH], PREFIX##_s_##SUFFIX); \ - LOAD_PIECE(SET.pieces[SOUTHWEST], PREFIX##_sw_##SUFFIX); \ - LOAD_PIECE(SET.pieces[WEST], PREFIX##_w_##SUFFIX); \ - LOAD_PIECE(SET.pieces[NORTHWEST], PREFIX##_nw_##SUFFIX) - - LOAD_PIECES(all_pieces[PIECE_A_HOLLOW],a,h); - LOAD_PIECES(all_pieces[PIECE_A_FILLED],a,f); - LOAD_PIECES(all_pieces[PIECE_B_HOLLOW],b,h); - LOAD_PIECES(all_pieces[PIECE_B_FILLED],b,f); - -# undef LOAD_PIECE -# undef LOAD_PIECES + spline *s = make_puzzle_curve (size); + XPoint *pts = (XPoint *) malloc (s->n_points * 4 * sizeof(*pts)); + int i, o; + + /* The border is twice as wide for "flat" edges, otherwise it looks funny. */ + if (fill_p) + bw = 0; + else + bw /= 2; + + o = 0; + if (top_type == 0) { + pts[o].x = x; pts[o].y = y + bw; o++; + pts[o].x = x + size; pts[o].y = y + bw; o++; + } else { + for (i = 0; i < s->n_points; i++) { + pts[o].x = x + s->points[i].x; + pts[o].y = y + s->points[i].y * top_type; + o++; + } + } + + if (right_type == 0) { + pts[o-1].x -= bw; + pts[o].x = x + size - bw; pts[o].y = y + size; o++; + } else { + for (i = 1; i < s->n_points; i++) { + pts[o].x = x + size + s->points[i].y * (-right_type); + pts[o].y = y + s->points[i].x; + o++; + } + } + + if (bottom_type == 0) { + pts[o-1].y -= bw; + pts[o].x = x; pts[o].y = y + size - bw; o++; + } else { + for (i = 1; i < s->n_points; i++) { + pts[o].x = x + s->points[s->n_points-i-1].x; + pts[o].y = y + size + s->points[s->n_points-i-1].y * (-bottom_type); + o++; + } + } + + if (left_type == 0) { + pts[o-1].x += bw; + pts[o].x = x + bw; pts[o].y = y; o++; + } else { + for (i = 1; i < s->n_points; i++) { + pts[o].x = x + s->points[s->n_points-i-1].y * left_type; + pts[o].y = y + s->points[s->n_points-i-1].x; + o++; + } + } + + free_spline (s); + + if (fill_p) + XFillPolygon (dpy, d, gc, pts, o, Complex, CoordModeOrigin); + else + XDrawLines (dpy, d, gc, pts, o, CoordModeOrigin); + + free (pts); } -static Pixmap -read_screen (Display *dpy, Window window, int *widthP, int *heightP) + +/* Creates two pixmaps for a puzzle piece: + - The first is a solid bit-mask with 1 for each pixel inside the piece; + - The second is an outline of the piece, where all drawn pixels are + contained within the mask. + + The top/right/bottom/left_type args indicate the direction the + tabs point: 1 for out, -1 for in, 0 for flat. + + Size is how big the piece should be, from origin to origin. + + Returned x/y is the origin within the pixmaps. + */ +static void +make_puzzle_pixmap_pair (Display *dpy, Drawable d, int size, int bw, + int top_type, int right_type, + int bottom_type, int left_type, + int *x_ret, int *y_ret, + Pixmap *mask_ret, Pixmap *outline_ret) { - Pixmap p; - XWindowAttributes xgwa; - XGetWindowAttributes (dpy, window, &xgwa); - *widthP = xgwa.width; - *heightP = xgwa.height; + int w = size * 3; + int h = w; + int x = size; + int y = size; + Pixmap p0 = XCreatePixmap (dpy, d, w, h, 1); + Pixmap p1 = XCreatePixmap (dpy, d, w, h, 1); + XGCValues gcv; + GC gc; + gcv.foreground = 0; + gcv.background = 0; + gc = XCreateGC (dpy, p0, GCForeground|GCBackground, &gcv); + XFillRectangle (dpy, p0, gc, 0, 0, w, h); + XFillRectangle (dpy, p1, gc, 0, 0, w, h); + XSetForeground (dpy, gc, 0); + +# ifdef HAVE_COCOA + jwxyz_XSetAlphaAllowed (dpy, gc, False); +# endif + + /* To ensure that each pixel is drawn only once, we render the piece + such that it "owns" the left and top edges, but not the right and + bottom edges. + + - - + "#" is this piece. + - # + It overlaps "-" and is overlapped by "+". + - + + + + To accomplish this, we clear to black, draw "#" in white, + then draw "+" in black. + */ - p = XCreatePixmap(dpy, window, *widthP, *heightP, xgwa.depth); - XClearWindow(dpy, window); - load_random_image (xgwa.screen, window, p, NULL, NULL); - XClearWindow(dpy, window); + /* Center square */ + XSetForeground (dpy, gc, 1); + draw_puzzle_shape (dpy, p0, gc, x, y, size, bw, + top_type, right_type, bottom_type, left_type, + True); + + /* Top right square */ + XSetForeground (dpy, gc, 0); + draw_puzzle_shape (dpy, p0, gc, x + size, y - size, size, bw, + 0, 0, -top_type, -left_type, + True); + + /* Center right square */ + draw_puzzle_shape (dpy, p0, gc, x + size, y, size, bw, + 0, 0, 0, -right_type, + True); + + /* Bottom center square */ + draw_puzzle_shape (dpy, p0, gc, x, y + size, size, bw, + -bottom_type, 0, 0, 0, + True); + + /* And Charles Nelson Reilly in the bottom right square */ + draw_puzzle_shape (dpy, p0, gc, x + size, y + size, size, bw, + -bottom_type, -right_type, 0, 0, + True); + + /* Done with p0 (the mask). + To make p1 (the outline) draw an outlined piece through the mask. + */ + if (bw < 0) + { + bw = size / 30; + if (bw < 1) bw = 1; + } + + if (bw > 0) + { + XSetForeground (dpy, gc, 1); + XSetClipMask (dpy, gc, p0); + XSetLineAttributes (dpy, gc, bw, LineSolid, CapButt, JoinRound); + draw_puzzle_shape (dpy, p1, gc, x, y, size, bw, + top_type, right_type, bottom_type, left_type, + False); + } - return p; + XFreeGC (dpy, gc); + *x_ret = x; + *y_ret = x; + *mask_ret = p0; + *outline_ret = p1; } -static int width, height; -static int x_border, y_border; -static Pixmap source; -static GC gc; -static Bool tweak; -static int fg, bg; -static XPoint *state = 0; +static void +make_puzzle_pixmaps (struct state *st) +{ + int i, j; + + int edges[9][4] = { + { -1, 1, -1, 1 }, /* CENTER */ + { 0, 1, -1, 1 }, /* NORTH */ + { 0, 0, -1, 1 }, /* NORTHEAST */ + { -1, 0, -1, 1 }, /* EAST */ + { -1, 0, 0, 1 }, /* SOUTHEAST */ + { -1, 1, 0, 1 }, /* SOUTH */ + { -1, 1, 0, 0 }, /* SOUTHWEST */ + { -1, 1, -1, 0 }, /* WEST */ + { 0, 1, -1, 0 }, /* NORTHWEST */ + }; + + /* sometimes swap direction of horizontal edges */ + if (random() & 1) + for (j = 0; j < countof(edges); j++) { + edges[j][0] = -edges[j][0]; + edges[j][2] = -edges[j][2]; + } + + /* sometimes swap direction of vertical edges */ + if (random() & 1) + for (j = 0; j < countof(edges); j++) { + edges[j][1] = -edges[j][1]; + edges[j][3] = -edges[j][3]; + } + + for (j = 0; j < 9; j++) { + for (i = 0; i < 2; i++) { + int x, y; + int top, right, bottom, left; + Pixmap mask, outline; + top = edges[j][0]; + right = edges[j][1]; + bottom = edges[j][2]; + left = edges[j][3]; + if (i) { + top = -top; + right = -right; + bottom = -bottom; + left = -left; + } + make_puzzle_pixmap_pair (st->dpy, st->window, st->piece_width, + st->border_width, + top, right, bottom, left, + &x, &y, &mask, &outline); + + st->all_pieces[i*2].pieces[j].x = x; + st->all_pieces[i*2].pieces[j].y = y; + st->all_pieces[i*2].pieces[j].pixmap = outline; + + st->all_pieces[i*2+1].pieces[j].x = x; + st->all_pieces[i*2+1].pieces[j].y = y; + st->all_pieces[i*2+1].pieces[j].pixmap = mask; + } + } +} + +static void +free_puzzle_pixmaps (struct state *st) +{ + int i, j; + for (i = 0; i < countof(st->all_pieces); i++) + for (j = 0; j < countof (st->all_pieces[i].pieces); j++) + if (st->all_pieces[i].pieces[j].pixmap) { + XFreePixmap (st->dpy, st->all_pieces[i].pieces[j].pixmap); + st->all_pieces[i].pieces[j].pixmap = 0; + } +} + static void -jigsaw_init(Display *dpy, Window window) +jigsaw_init_1 (struct state *st) { XWindowAttributes xgwa; int x, y; XGCValues gcv; Colormap cmap; - int source_w, source_h; - tweak = random()&1; + XGetWindowAttributes (st->dpy, st->window, &xgwa); + + st->piece_width = 40 + (random() % 100); + if (xgwa.width / st->piece_width < 4) + st->piece_width = xgwa.width / 4; + st->piece_height = st->piece_width; - source = read_screen (dpy, window, &source_w, &source_h); + free_puzzle_pixmaps (st); + make_puzzle_pixmaps (st); - XGetWindowAttributes (dpy, window, &xgwa); cmap = xgwa.colormap; - width = xgwa.width / GRID_WIDTH; - height = xgwa.height / GRID_HEIGHT; - x_border = (xgwa.width - (width * GRID_WIDTH)) / 2; - y_border = (xgwa.height - (height * GRID_WIDTH)) / 2; + st->width = xgwa.width / st->piece_width; + st->height = xgwa.height / st->piece_height; + st->x_border = (xgwa.width - (st->width * st->piece_width)) / 2; + st->y_border = (xgwa.height - (st->height * st->piece_width)) / 2; - if (width < 4 || height < 4) + if (st->width < 4) st->width = 4, st->x_border = 0; + if (st->height < 4) st->height = 4, st->y_border = 0; + + if (st->state) free (st->state); + st->state = (XPoint *) malloc (st->width * st->height * sizeof(*st->state)); + + if (!st->gc) { - fprintf (stderr, "%s: window too small: %dx%d (need at least %dx%d)\n", - progname, xgwa.width, xgwa.height, - GRID_WIDTH * 4, GRID_HEIGHT * 4); - exit (1); + XColor fgc, bgc; + char *fgs = get_string_resource(st->dpy, "foreground", "Foreground"); + char *bgs = get_string_resource(st->dpy, "background", "Background"); + Bool fg_ok, bg_ok; + + st->gc = XCreateGC (st->dpy, st->window, 0, &gcv); + +# ifdef HAVE_COCOA + jwxyz_XSetAlphaAllowed (st->dpy, st->gc, False); +# endif + + if (!XParseColor (st->dpy, cmap, fgs, &fgc)) + XParseColor (st->dpy, cmap, "gray", &fgc); + if (!XParseColor (st->dpy, cmap, bgs, &bgc)) + XParseColor (st->dpy, cmap, "black", &bgc); + + free (fgs); + free (bgs); + fgs = bgs = 0; + + fg_ok = XAllocColor (st->dpy, cmap, &fgc); + bg_ok = XAllocColor (st->dpy, cmap, &bgc); + + /* If we weren't able to allocate the two colors we want from the + colormap (which is likely if the screen has been grabbed on an + 8-bit SGI visual -- don't ask) then just go through the map + and find the closest color to the ones we wanted, and use those + pixels without actually allocating them. + */ + if (fg_ok) + st->fg = fgc.pixel; + else + st->fg = 0; + + if (bg_ok) + st->bg = bgc.pixel; + else + st->bg = 1; + +#ifndef HAVE_COCOA + if (!fg_ok || bg_ok) + { + int i; + unsigned long fgd = ~0; + unsigned long bgd = ~0; + int max = visual_cells (xgwa.screen, xgwa.visual); + XColor *all = (XColor *) calloc(sizeof (*all), max); + for (i = 0; i < max; i++) + { + all[i].flags = DoRed|DoGreen|DoBlue; + all[i].pixel = i; + } + XQueryColors (st->dpy, cmap, all, max); + for(i = 0; i < max; i++) + { + long rd, gd, bd; + unsigned long d; + if (!fg_ok) + { + rd = (all[i].red >> 8) - (fgc.red >> 8); + gd = (all[i].green >> 8) - (fgc.green >> 8); + bd = (all[i].blue >> 8) - (fgc.blue >> 8); + if (rd < 0) rd = -rd; + if (gd < 0) gd = -gd; + if (bd < 0) bd = -bd; + d = (rd << 1) + (gd << 2) + bd; + if (d < fgd) + { + fgd = d; + st->fg = all[i].pixel; + if (d == 0) + fg_ok = True; + } + } + + if (!bg_ok) + { + rd = (all[i].red >> 8) - (bgc.red >> 8); + gd = (all[i].green >> 8) - (bgc.green >> 8); + bd = (all[i].blue >> 8) - (bgc.blue >> 8); + if (rd < 0) rd = -rd; + if (gd < 0) gd = -gd; + if (bd < 0) bd = -bd; + d = (rd << 1) + (gd << 2) + bd; + if (d < bgd) + { + bgd = d; + st->bg = all[i].pixel; + if (d == 0) + bg_ok = True; + } + } + + if (fg_ok && bg_ok) + break; + } + XFree(all); + } +#endif /* HAVE_COCOA */ } - if (!state) - state = (XPoint *) malloc(width * height * sizeof(XPoint)); - gc = XCreateGC (dpy, window, 0, &gcv); - - { - XColor fgc, bgc; - char *fgs = get_string_resource("foreground", "Foreground"); - char *bgs = get_string_resource("background", "Background"); - Bool fg_ok, bg_ok; - if (!XParseColor (dpy, cmap, fgs, &fgc)) - XParseColor (dpy, cmap, "gray", &fgc); - if (!XParseColor (dpy, cmap, bgs, &bgc)) - XParseColor (dpy, cmap, "black", &bgc); - - fg_ok = XAllocColor (dpy, cmap, &fgc); - bg_ok = XAllocColor (dpy, cmap, &bgc); - - /* If we weren't able to allocate the two colors we want from the - colormap (which is likely if the screen has been grabbed on an - 8-bit SGI visual -- don't ask) then just go through the map - and find the closest color to the ones we wanted, and use those - pixels without actually allocating them. - */ - if (fg_ok) - fg = fgc.pixel; - else - fg = 0; - - if (bg_ok) - bg = bgc.pixel; - else - bg = 1; + /* Reset the window's background color... */ + XSetWindowBackground (st->dpy, st->window, st->bg); + XClearWindow(st->dpy, st->window); - if (!fg_ok || bg_ok) + for (y = 0; y < st->height; y++) + for (x = 0; x < st->width; x++) { - int i; - unsigned long fgd = ~0; - unsigned long bgd = ~0; - int max = visual_cells (xgwa.screen, xgwa.visual); - XColor *all = (XColor *) calloc(sizeof (*all), max); - for (i = 0; i < max; i++) - { - all[i].flags = DoRed|DoGreen|DoBlue; - all[i].pixel = i; - } - XQueryColors (dpy, cmap, all, max); - for(i = 0; i < max; i++) - { - long rd, gd, bd; - unsigned long d; - if (!fg_ok) - { - rd = (all[i].red >> 8) - (fgc.red >> 8); - gd = (all[i].green >> 8) - (fgc.green >> 8); - bd = (all[i].blue >> 8) - (fgc.blue >> 8); - if (rd < 0) rd = -rd; - if (gd < 0) gd = -gd; - if (bd < 0) bd = -bd; - d = (rd << 1) + (gd << 2) + bd; - if (d < fgd) - { - fgd = d; - fg = all[i].pixel; - if (d == 0) - fg_ok = True; - } - } - - if (!bg_ok) - { - rd = (all[i].red >> 8) - (bgc.red >> 8); - gd = (all[i].green >> 8) - (bgc.green >> 8); - bd = (all[i].blue >> 8) - (bgc.blue >> 8); - if (rd < 0) rd = -rd; - if (gd < 0) gd = -gd; - if (bd < 0) bd = -bd; - d = (rd << 1) + (gd << 2) + bd; - if (d < bgd) - { - bgd = d; - bg = all[i].pixel; - if (d == 0) - bg_ok = True; - } - } - - if (fg_ok && bg_ok) - break; - } - XFree(all); + st->state[y * st->width + x].x = x; + st->state[y * st->width + x].y = y; } - } - /* Reset the window's background color... */ - XSetWindowBackground (dpy, window, bg); - XClearWindow(dpy, window); + if (st->source) + XFreePixmap (st->dpy, st->source); + st->source = XCreatePixmap (st->dpy, st->window, xgwa.width, xgwa.height, + xgwa.depth); - for (y = 0; y < height; y++) - for (x = 0; x < width; x++) - { - state[y * width + x].x = x; - state[y * width + x].y = y; - } + st->img_loader = load_image_async_simple (0, xgwa.screen, st->window, + st->source, 0, 0); } static void -get_piece(int x, int y, struct piece **hollow, struct piece **filled) +get_piece (struct state *st, + int x, int y, struct piece **hollow, struct piece **filled) { int p; Bool which = (x & 1) == (y & 1); - if (x == 0 && y == 0) p = NORTHWEST; - else if (x == width-1 && y == 0) p = NORTHEAST; - else if (x == width-1 && y == height-1) p = SOUTHEAST; - else if (x == 0 && y == height-1) p = SOUTHWEST; - else if (y == 0) p = NORTH; - else if (x == width-1) p = EAST; - else if (y == height-1) p = SOUTH; - else if (x == 0) p = WEST; - else p = CENTER; - - if (tweak) which = !which; + if (x == 0 && y == 0) p = NORTHWEST; + else if (x == st->width-1 && y == 0) p = NORTHEAST; + else if (x == st->width-1 && y == st->height-1) p = SOUTHEAST; + else if (x == 0 && y == st->height-1) p = SOUTHWEST; + else if (y == 0) p = NORTH; + else if (x == st->width-1) p = EAST; + else if (y == st->height-1) p = SOUTH; + else if (x == 0) p = WEST; + else p = CENTER; + if (hollow) *hollow = (which - ? &all_pieces[PIECE_A_HOLLOW].pieces[p] - : &all_pieces[PIECE_B_HOLLOW].pieces[p]); + ? &st->all_pieces[PIECE_A_HOLLOW].pieces[p] + : &st->all_pieces[PIECE_B_HOLLOW].pieces[p]); if (filled) *filled = (which - ? &all_pieces[PIECE_A_FILLED].pieces[p] - : &all_pieces[PIECE_B_FILLED].pieces[p]); + ? &st->all_pieces[PIECE_A_FILLED].pieces[p] + : &st->all_pieces[PIECE_B_FILLED].pieces[p]); } static void -draw_piece(Display *dpy, Window window, int x, int y, int clear_p) +draw_piece (struct state *st, int x, int y, int clear_p) { struct piece *hollow, *filled; - int from_x = state[y * width + x].x; - int from_y = state[y * width + x].y; + int from_x = st->state[y * st->width + x].x; + int from_y = st->state[y * st->width + x].y; - get_piece(x, y, &hollow, &filled); + get_piece(st, x, y, &hollow, &filled); - XSetClipMask(dpy, gc, filled->pixmap); - XSetClipOrigin(dpy, gc, - x_border + (x * GRID_WIDTH) - filled->x - 1, - y_border + (y * GRID_WIDTH) - filled->y - 1); + XSetClipMask(st->dpy, st->gc, filled->pixmap); + XSetClipOrigin(st->dpy, st->gc, + st->x_border + (x * st->piece_width) - filled->x - 1, + st->y_border + (y * st->piece_width) - filled->y - 1); if (clear_p) { - XSetForeground(dpy, gc, bg); - XFillRectangle(dpy, window, gc, - x_border + (x * GRID_WIDTH) - GRID_WIDTH/2, - y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2, - GRID_WIDTH*2, GRID_HEIGHT*2); + XSetForeground(st->dpy, st->gc, st->bg); + XFillRectangle(st->dpy, st->window, st->gc, + st->x_border + (x * st->piece_width) -st->piece_width/2, + st->y_border + (y * st->piece_height) -st->piece_height/2, + st->piece_width*2, st->piece_height*2); } else - XCopyArea(dpy, source, window, gc, - x_border + (from_x * GRID_WIDTH) - GRID_WIDTH/2, - y_border + (from_y * GRID_HEIGHT) - GRID_HEIGHT/2, - GRID_WIDTH*2, GRID_HEIGHT*2, - x_border + (x * GRID_WIDTH) - GRID_WIDTH/2, - y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2); + XCopyArea(st->dpy, st->source, st->window, st->gc, + st->x_border + (from_x * st->piece_width) - st->piece_width/2, + st->y_border + (from_y * st->piece_height) - st->piece_height/2, + st->piece_width*2, st->piece_height*2, + st->x_border + (x * st->piece_width) - st->piece_width/2, + st->y_border + (y * st->piece_height) - st->piece_height/2); if (clear_p > 1) return; - XSetForeground(dpy, gc, fg); - XSetClipMask(dpy, gc, hollow->pixmap); - XSetClipOrigin(dpy, gc, - x_border + (x * GRID_WIDTH) - hollow->x - 1, - y_border + (y * GRID_WIDTH) - hollow->y - 1); - XFillRectangle(dpy, window, gc, - x_border + (x * GRID_WIDTH) - GRID_WIDTH/2, - y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2, - GRID_WIDTH*2, GRID_HEIGHT*2); - - if (clear_p) - { - /* If the pieces lined up right, we could do this by just not drawing - the outline -- but that doesn't look right, since it eats the outlines - of the adjascent pieces. So draw the outline, then chop off the outer - edge if this is a border piece. - */ - XSetForeground(dpy, gc, bg); - if (x == 0) - XFillRectangle(dpy, window, gc, - x_border - 2, - y_border + (y * GRID_HEIGHT), - 3, GRID_HEIGHT); - else if (x == width-1) - XFillRectangle(dpy, window, gc, - x_border + ((x+1) * GRID_WIDTH) - 2, - y_border + (y * GRID_HEIGHT), - 3, GRID_HEIGHT); - - if (y == 0) - XFillRectangle(dpy, window, gc, - x_border + (x * GRID_WIDTH), - y_border - 2, - GRID_WIDTH, 3); - else if (y == height-1) - XFillRectangle(dpy, window, gc, - x_border + (x * GRID_WIDTH), - y_border + ((y+1) * GRID_HEIGHT) - 2, - GRID_WIDTH, 3); - } + XSetForeground(st->dpy, st->gc, st->fg); + XSetClipMask(st->dpy, st->gc, hollow->pixmap); + XSetClipOrigin(st->dpy, st->gc, + st->x_border + (x * st->piece_width) - hollow->x - 1, + st->y_border + (y * st->piece_width) - hollow->y - 1); + XFillRectangle(st->dpy, st->window, st->gc, + st->x_border + (x * st->piece_width) - st->piece_width/2, + st->y_border + (y * st->piece_height) - st->piece_height/2, + st->piece_width*2, st->piece_height*2); } -static void -swap_pieces(Display *dpy, Window window, - int src_x, int src_y, int dst_x, int dst_y, - Bool draw_p) +static int +animate_swap (struct state *st, struct swap_state *sw) { XPoint swap; - int i; - if (draw_p) - for (i = 0; i < 3; i++) - { - draw_piece(dpy, window, src_x, src_y, 1); - draw_piece(dpy, window, dst_x, dst_y, 1); - XSync(dpy, False); - usleep(50000); - draw_piece(dpy, window, src_x, src_y, 0); - draw_piece(dpy, window, dst_x, dst_y, 0); - XSync(dpy, False); - usleep(50000); - } - swap = state[src_y * width + src_x]; - state[src_y * width + src_x] = state[dst_y * width + dst_x]; - state[dst_y * width + dst_x] = swap; + if (sw->flashing > 1) + { + draw_piece(st, sw->x1, sw->y1, sw->flashing & 1); + draw_piece(st, sw->x2, sw->y2, sw->flashing & 1); + sw->flashing--; + return st->delay; + } - if (draw_p) + swap = st->state[sw->y1 * st->width + sw->x1]; + st->state[sw->y1 * st->width + sw->x1] = + st->state[sw->y2 * st->width + sw->x2]; + st->state[sw->y2 * st->width + sw->x2] = swap; + + if (sw->draw_p) { - draw_piece(dpy, window, src_x, src_y, 0); - draw_piece(dpy, window, dst_x, dst_y, 0); - XSync(dpy, False); + draw_piece(st, sw->x1, sw->y1, 0); + draw_piece(st, sw->x2, sw->y2, 0); + sw->flashing = 0; } + + return 0; } -static void -shuffle(Display *dpy, Window window, Bool draw_p) +static int +swap_pieces (struct state *st, + int src_x, int src_y, int dst_x, int dst_y, + Bool draw_p) +{ + struct swap_state *sw = &st->swap; + + sw->x1 = src_x; + sw->y1 = src_y; + sw->x2 = dst_x; + sw->y2 = dst_y; + sw->draw_p = draw_p; + + /* if animating, plan to flash the pieces on and off a few times */ + sw->flashing = sw->draw_p ? 7 : 0; + + return animate_swap(st, sw); +} + + +static Bool +done (struct state *st) +{ + int x, y; + for (y = 0; y < st->height; y++) + for (x = 0; x < st->width; x++) + { + int x2 = st->state[y * st->width + x].x; + int y2 = st->state[y * st->width + x].y; + if (x != x2 || y != y2) + return False; + } + return True; +} + + +static int +shuffle (struct state *st, Bool draw_p) { struct piece *p1, *p2; int src_x, src_y, dst_x = -1, dst_y = -1; AGAIN: p1 = p2 = 0; - src_x = random() % width; - src_y = random() % height; + src_x = random() % st->width; + src_y = random() % st->height; - get_piece(src_x, src_y, &p1, 0); + get_piece(st, src_x, src_y, &p1, 0); /* Pick random coordinates until we find one that has the same kind of piece as the first one we picked. Note that it's possible for there - to be only one piece of a particular shape on the board (this commonly - happens with the corner pieces.) + to be only one piece of a particular shape on the board (this always + happens with the four corner pieces.) */ while (p1 != p2) { - dst_x = random() % width; - dst_y = random() % height; - get_piece(dst_x, dst_y, &p2, 0); + dst_x = random() % st->width; + dst_y = random() % st->height; + get_piece(st, dst_x, dst_y, &p2, 0); } if (src_x == dst_x && src_y == dst_y) goto AGAIN; - swap_pieces(dpy, window, src_x, src_y, dst_x, dst_y, draw_p); + return swap_pieces(st, src_x, src_y, dst_x, dst_y, draw_p); } static void -shuffle_all(Display *dpy, Window window) +shuffle_all (struct state *st) { - int i = (width * height * 10); - while (i > 0) - { - shuffle(dpy, window, False); - i--; - } + int j; + for (j = 0; j < 5; j++) { + /* swap each piece with another 5x */ + int i = (st->width * st->height * 5); + while (--i > 0) + shuffle (st, False); + + /* and do that whole process up to 5x if we ended up with a solved + board (this often happens with 4x4 boards.) */ + if (!done(st)) + break; + } } -static void -unshuffle(Display *dpy, Window window) + +static int +unshuffle (struct state *st) { int i; - for (i = 0; i < width * height * 4; i++) + for (i = 0; i < st->width * st->height * 4; i++) { - int x = random() % width; - int y = random() % height; - int x2 = state[y * width + x].x; - int y2 = state[y * width + x].y; + int x = random() % st->width; + int y = random() % st->height; + int x2 = st->state[y * st->width + x].x; + int y2 = st->state[y * st->width + x].y; if (x != x2 || y != y2) { - swap_pieces(dpy, window, x, y, x2, y2, True); - break; + return swap_pieces(st, x, y, x2, y2, True); } } + return 0; } -static void -clear_all(Display *dpy, Window window) + +static int +animate_clear (struct state *st) { - int n = width * height; - while (n > 0) + while (st->clearing > 0) { - int x = random() % width; - int y = random() % height; - XPoint *p = &state[y * width + x]; + int x = random() % st->width; + int y = random() % st->height; + XPoint *p = &st->state[y * st->width + x]; if (p->x == -1) continue; - draw_piece(dpy, window, p->x, p->y, 2); - XSync(dpy, False); - usleep(1000); + draw_piece(st, p->x, p->y, 2); p->x = p->y = -1; - n--; + st->clearing--; + return st->delay; } + return 0; } -static Bool -done(void) + +static int +clear_all (struct state *st) +{ + st->clearing = st->width * st->height; + return animate_clear(st); +} + + +static void * +jigsaw_init (Display *dpy, Window window) { + struct state *st = (struct state *) calloc (1, sizeof(*st)); + st->dpy = dpy; + st->window = window; + st->delay = get_integer_resource (st->dpy, "delay", "Integer"); + st->delay2 = get_integer_resource (st->dpy, "delay2", "Integer") * 1000000; + st->border_width = get_integer_resource (st->dpy, "pieceBorderWidth", + "Integer"); + if (st->delay == 0) st->delay = 1; /* kludge */ + return st; +} + + +static unsigned long +jigsaw_draw (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; int x, y; - for (y = 0; y < height; y++) - for (x = 0; x < width; x++) + int delay = 0; + + if (st->img_loader) /* still loading */ + { + st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0); + if (! st->img_loader) { /* just finished */ + shuffle_all (st); + for (y = 0; y < st->height; y++) + for (x = 0; x < st->width; x++) + draw_piece(st, x, y, 0); + } + return st->delay; + } + + if (st->swap.flashing) + delay = animate_swap (st, &st->swap); + else if (st->clearing) + delay = animate_clear (st); + + if (!delay) { + if (st->jigstate == 0) { - int x2 = state[y * width + x].x; - int y2 = state[y * width + x].y; - if (x != x2 || y != y2) - return False; + jigsaw_init_1 (st); + st->jigstate = 1; } - return True; + else if (st->jigstate == 1) + { + if (done(st)) + { + st->jigstate = 2; + delay = st->delay2; + } + else + { + delay = unshuffle(st); + } + } + else if (st->jigstate == 2) + { + st->jigstate = 0; + delay = clear_all(st); + } + else + abort(); + } + + if (delay == 1) delay = 0; /* kludge */ + return (delay ? delay : st->delay * 10); +} + +static void +jigsaw_reshape (Display *dpy, Window window, void *closure, + unsigned int w, unsigned int h) +{ + /* window size is checked each time a new puzzle begins */ } +static Bool +jigsaw_event (Display *dpy, Window window, void *closure, XEvent *event) +{ + return False; +} + +static void +jigsaw_free (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; + free_puzzle_pixmaps (st); + if (st->state) free (st->state); + if (st->gc) XFreeGC (dpy, st->gc); + if (st->source) XFreePixmap (dpy, st->source); + free (st); +} -char *progclass = "Jigsaw"; -char *defaults [] = { +static const char *jigsaw_defaults [] = { ".background: Black", - ".foreground: Gray40", + ".foreground: #AAAAAA", "*delay: 70000", "*delay2: 5", + "*pieceBorderWidth: -1", #ifdef __sgi /* really, HAVE_READ_DISPLAY_EXTENSION */ "*visualID: Best", #endif 0 }; -XrmOptionDescRec options [] = { +static XrmOptionDescRec jigsaw_options [] = { { "-delay", ".delay", XrmoptionSepArg, 0 }, { "-delay2", ".delay2", XrmoptionSepArg, 0 }, + { "-bw", ".pieceBorderWidth", XrmoptionSepArg, 0 }, + { "-border-width", ".pieceBorderWidth", XrmoptionSepArg, 0 }, { 0, 0, 0, 0 } }; -void -screenhack (Display *dpy, Window window) -{ - int delay = get_integer_resource("delay", "Integer"); - int delay2 = get_integer_resource("delay2", "Integer"); - - init_images(dpy, window); - while (1) - { - int x, y; - jigsaw_init (dpy, window); - shuffle_all(dpy, window); - - for (y = 0; y < height; y++) - for (x = 0; x < width; x++) - draw_piece(dpy, window, x, y, 0); - - while (!done()) - { - unshuffle(dpy, window); - XSync (dpy, False); - screenhack_handle_events (dpy); - if (delay) usleep (delay); - } - - screenhack_handle_events (dpy); - if (delay2) - usleep (delay2 * 1000000); - - clear_all(dpy, window); - } -} +XSCREENSAVER_MODULE ("Jigsaw", jigsaw)