http://slackware.bholcomb.com/slackware/slackware-11.0/source/xap/xscreensaver/xscree...
[xscreensaver] / hacks / jigsaw.c
index 9d7506db5de793d749d7c95cc3c93928127d6e53..dce647bcc4e21307b67c7133b13dfd6efe42cd5e 100644 (file)
@@ -1,5 +1,4 @@
-/* xscreensaver, Copyright (c) 1997, 1998, 2001, 2003, 2005
- *  Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1997-2006 Jamie Zawinski <jwz@jwz.org>
  *
  * 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.
 
  */
 
 #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);
+}
 
 \f
-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)