1 /* xscreensaver, Copyright (c) 1997-2006 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
15 = Rather than just flickering the pieces before swapping them,
16 show them lifting up and moving to their new positions.
17 The path on which they move shouldn't be a straight line;
18 try to avoid having them cross each other by moving them in
19 oppositely-positioned arcs.
21 = Rotate the pieces as well, so that we can swap the corner
22 and edge pieces with each other.
24 = Have it drop all pieces to the "floor" then pick them up to
25 reassemble the picture.
27 = As a joke, maybe sometimes have one piece that doesn't fit?
31 #include "screenhack.h"
35 #define countof(x) (sizeof((x))/sizeof((*x)))
54 struct piece pieces[9];
57 #define PIECE_A_HOLLOW 0
58 #define PIECE_A_FILLED 1
59 #define PIECE_B_HOLLOW 2
60 #define PIECE_B_FILLED 3
72 struct set all_pieces[4];
74 int piece_width, piece_height;
76 int x_border, y_border;
86 struct swap_state swap;
89 async_load_state *img_loader;
93 /* Returns a spline describing one edge of a puzzle piece of the given length.
96 make_puzzle_curve (int pixels)
98 double x0 = 0.0000, y0 = 0.0000;
99 double x1 = 0.3333, y1 = 0.1000;
100 double x2 = 0.4333, y2 = 0.0333;
101 double x3 = 0.4666, y3 = -0.0666;
102 double x4 = 0.3333, y4 = -0.1666;
103 double x5 = 0.3666, y5 = -0.2900;
104 double x6 = 0.5000, y6 = -0.3333;
106 spline *s = make_spline(20);
110 s->control_x[s->n_controls] = pixels * (x); \
111 s->control_y[s->n_controls] = pixels * (y); \
133 /* Draws a puzzle piece. The top/right/bottom/left_type args
134 indicate the direction the tabs point: 1 for out, -1 for in, 0 for flat.
137 draw_puzzle_shape (Display *dpy, Drawable d, GC gc,
138 int x, int y, int size, int bw,
139 int top_type, int right_type,
140 int bottom_type, int left_type,
143 spline *s = make_puzzle_curve (size);
144 XPoint *pts = (XPoint *) malloc (s->n_points * 4 * sizeof(*pts));
147 /* The border is twice as wide for "flat" edges, otherwise it looks funny. */
155 pts[o].x = x; pts[o].y = y + bw; o++;
156 pts[o].x = x + size; pts[o].y = y + bw; o++;
158 for (i = 0; i < s->n_points; i++) {
159 pts[o].x = x + s->points[i].x;
160 pts[o].y = y + s->points[i].y * top_type;
165 if (right_type == 0) {
167 pts[o].x = x + size - bw; pts[o].y = y + size; o++;
169 for (i = 1; i < s->n_points; i++) {
170 pts[o].x = x + size + s->points[i].y * (-right_type);
171 pts[o].y = y + s->points[i].x;
176 if (bottom_type == 0) {
178 pts[o].x = x; pts[o].y = y + size - bw; o++;
180 for (i = 1; i < s->n_points; i++) {
181 pts[o].x = x + s->points[s->n_points-i-1].x;
182 pts[o].y = y + size + s->points[s->n_points-i-1].y * (-bottom_type);
187 if (left_type == 0) {
189 pts[o].x = x + bw; pts[o].y = y; o++;
191 for (i = 1; i < s->n_points; i++) {
192 pts[o].x = x + s->points[s->n_points-i-1].y * left_type;
193 pts[o].y = y + s->points[s->n_points-i-1].x;
201 XFillPolygon (dpy, d, gc, pts, o, Complex, CoordModeOrigin);
203 XDrawLines (dpy, d, gc, pts, o, CoordModeOrigin);
209 /* Creates two pixmaps for a puzzle piece:
210 - The first is a solid bit-mask with 1 for each pixel inside the piece;
211 - The second is an outline of the piece, where all drawn pixels are
212 contained within the mask.
214 The top/right/bottom/left_type args indicate the direction the
215 tabs point: 1 for out, -1 for in, 0 for flat.
217 Size is how big the piece should be, from origin to origin.
219 Returned x/y is the origin within the pixmaps.
222 make_puzzle_pixmap_pair (Display *dpy, Drawable d, int size, int bw,
223 int top_type, int right_type,
224 int bottom_type, int left_type,
225 int *x_ret, int *y_ret,
226 Pixmap *mask_ret, Pixmap *outline_ret)
232 Pixmap p0 = XCreatePixmap (dpy, d, w, h, 1);
233 Pixmap p1 = XCreatePixmap (dpy, d, w, h, 1);
238 gc = XCreateGC (dpy, p0, GCForeground|GCBackground, &gcv);
239 XFillRectangle (dpy, p0, gc, 0, 0, w, h);
240 XFillRectangle (dpy, p1, gc, 0, 0, w, h);
241 XSetForeground (dpy, gc, 0);
244 jwxyz_XSetAlphaAllowed (dpy, gc, False);
247 /* To ensure that each pixel is drawn only once, we render the piece
248 such that it "owns" the left and top edges, but not the right and
251 - - + "#" is this piece.
252 - # + It overlaps "-" and is overlapped by "+".
255 To accomplish this, we clear to black, draw "#" in white,
256 then draw "+" in black.
260 XSetForeground (dpy, gc, 1);
261 draw_puzzle_shape (dpy, p0, gc, x, y, size, bw,
262 top_type, right_type, bottom_type, left_type,
265 /* Top right square */
266 XSetForeground (dpy, gc, 0);
267 draw_puzzle_shape (dpy, p0, gc, x + size, y - size, size, bw,
268 0, 0, -top_type, -left_type,
271 /* Center right square */
272 draw_puzzle_shape (dpy, p0, gc, x + size, y, size, bw,
273 0, 0, 0, -right_type,
276 /* Bottom center square */
277 draw_puzzle_shape (dpy, p0, gc, x, y + size, size, bw,
278 -bottom_type, 0, 0, 0,
281 /* And Charles Nelson Reilly in the bottom right square */
282 draw_puzzle_shape (dpy, p0, gc, x + size, y + size, size, bw,
283 -bottom_type, -right_type, 0, 0,
286 /* Done with p0 (the mask).
287 To make p1 (the outline) draw an outlined piece through the mask.
297 XSetForeground (dpy, gc, 1);
298 XSetClipMask (dpy, gc, p0);
299 XSetLineAttributes (dpy, gc, bw, LineSolid, CapButt, JoinRound);
300 draw_puzzle_shape (dpy, p1, gc, x, y, size, bw,
301 top_type, right_type, bottom_type, left_type,
314 make_puzzle_pixmaps (struct state *st)
319 { -1, 1, -1, 1 }, /* CENTER */
320 { 0, 1, -1, 1 }, /* NORTH */
321 { 0, 0, -1, 1 }, /* NORTHEAST */
322 { -1, 0, -1, 1 }, /* EAST */
323 { -1, 0, 0, 1 }, /* SOUTHEAST */
324 { -1, 1, 0, 1 }, /* SOUTH */
325 { -1, 1, 0, 0 }, /* SOUTHWEST */
326 { -1, 1, -1, 0 }, /* WEST */
327 { 0, 1, -1, 0 }, /* NORTHWEST */
330 /* sometimes swap direction of horizontal edges */
332 for (j = 0; j < countof(edges); j++) {
333 edges[j][0] = -edges[j][0];
334 edges[j][2] = -edges[j][2];
337 /* sometimes swap direction of vertical edges */
339 for (j = 0; j < countof(edges); j++) {
340 edges[j][1] = -edges[j][1];
341 edges[j][3] = -edges[j][3];
344 for (j = 0; j < 9; j++) {
345 for (i = 0; i < 2; i++) {
347 int top, right, bottom, left;
348 Pixmap mask, outline;
351 bottom = edges[j][2];
359 make_puzzle_pixmap_pair (st->dpy, st->window, st->piece_width,
361 top, right, bottom, left,
362 &x, &y, &mask, &outline);
364 st->all_pieces[i*2].pieces[j].x = x;
365 st->all_pieces[i*2].pieces[j].y = y;
366 st->all_pieces[i*2].pieces[j].pixmap = outline;
368 st->all_pieces[i*2+1].pieces[j].x = x;
369 st->all_pieces[i*2+1].pieces[j].y = y;
370 st->all_pieces[i*2+1].pieces[j].pixmap = mask;
376 free_puzzle_pixmaps (struct state *st)
379 for (i = 0; i < countof(st->all_pieces); i++)
380 for (j = 0; j < countof (st->all_pieces[i].pieces); j++)
381 if (st->all_pieces[i].pieces[j].pixmap) {
382 XFreePixmap (st->dpy, st->all_pieces[i].pieces[j].pixmap);
383 st->all_pieces[i].pieces[j].pixmap = 0;
389 jigsaw_init_1 (struct state *st)
391 XWindowAttributes xgwa;
396 XGetWindowAttributes (st->dpy, st->window, &xgwa);
398 st->piece_width = 40 + (random() % 100);
399 if (xgwa.width / st->piece_width < 4)
400 st->piece_width = xgwa.width / 4;
401 st->piece_height = st->piece_width;
403 free_puzzle_pixmaps (st);
404 make_puzzle_pixmaps (st);
406 cmap = xgwa.colormap;
407 st->width = xgwa.width / st->piece_width;
408 st->height = xgwa.height / st->piece_height;
409 st->x_border = (xgwa.width - (st->width * st->piece_width)) / 2;
410 st->y_border = (xgwa.height - (st->height * st->piece_width)) / 2;
412 if (st->width < 4) st->width = 4, st->x_border = 0;
413 if (st->height < 4) st->height = 4, st->y_border = 0;
415 if (st->state) free (st->state);
416 st->state = (XPoint *) malloc (st->width * st->height * sizeof(*st->state));
421 char *fgs = get_string_resource(st->dpy, "foreground", "Foreground");
422 char *bgs = get_string_resource(st->dpy, "background", "Background");
425 st->gc = XCreateGC (st->dpy, st->window, 0, &gcv);
428 jwxyz_XSetAlphaAllowed (st->dpy, st->gc, False);
431 if (!XParseColor (st->dpy, cmap, fgs, &fgc))
432 XParseColor (st->dpy, cmap, "gray", &fgc);
433 if (!XParseColor (st->dpy, cmap, bgs, &bgc))
434 XParseColor (st->dpy, cmap, "black", &bgc);
440 fg_ok = XAllocColor (st->dpy, cmap, &fgc);
441 bg_ok = XAllocColor (st->dpy, cmap, &bgc);
443 /* If we weren't able to allocate the two colors we want from the
444 colormap (which is likely if the screen has been grabbed on an
445 8-bit SGI visual -- don't ask) then just go through the map
446 and find the closest color to the ones we wanted, and use those
447 pixels without actually allocating them.
463 unsigned long fgd = ~0;
464 unsigned long bgd = ~0;
465 int max = visual_cells (xgwa.screen, xgwa.visual);
466 XColor *all = (XColor *) calloc(sizeof (*all), max);
467 for (i = 0; i < max; i++)
469 all[i].flags = DoRed|DoGreen|DoBlue;
472 XQueryColors (st->dpy, cmap, all, max);
473 for(i = 0; i < max; i++)
479 rd = (all[i].red >> 8) - (fgc.red >> 8);
480 gd = (all[i].green >> 8) - (fgc.green >> 8);
481 bd = (all[i].blue >> 8) - (fgc.blue >> 8);
482 if (rd < 0) rd = -rd;
483 if (gd < 0) gd = -gd;
484 if (bd < 0) bd = -bd;
485 d = (rd << 1) + (gd << 2) + bd;
489 st->fg = all[i].pixel;
497 rd = (all[i].red >> 8) - (bgc.red >> 8);
498 gd = (all[i].green >> 8) - (bgc.green >> 8);
499 bd = (all[i].blue >> 8) - (bgc.blue >> 8);
500 if (rd < 0) rd = -rd;
501 if (gd < 0) gd = -gd;
502 if (bd < 0) bd = -bd;
503 d = (rd << 1) + (gd << 2) + bd;
507 st->bg = all[i].pixel;
518 #endif /* HAVE_COCOA */
521 /* Reset the window's background color... */
522 XSetWindowBackground (st->dpy, st->window, st->bg);
523 XClearWindow(st->dpy, st->window);
525 for (y = 0; y < st->height; y++)
526 for (x = 0; x < st->width; x++)
528 st->state[y * st->width + x].x = x;
529 st->state[y * st->width + x].y = y;
533 XFreePixmap (st->dpy, st->source);
534 st->source = XCreatePixmap (st->dpy, st->window, xgwa.width, xgwa.height,
537 st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
543 get_piece (struct state *st,
544 int x, int y, struct piece **hollow, struct piece **filled)
547 Bool which = (x & 1) == (y & 1);
549 if (x == 0 && y == 0) p = NORTHWEST;
550 else if (x == st->width-1 && y == 0) p = NORTHEAST;
551 else if (x == st->width-1 && y == st->height-1) p = SOUTHEAST;
552 else if (x == 0 && y == st->height-1) p = SOUTHWEST;
553 else if (y == 0) p = NORTH;
554 else if (x == st->width-1) p = EAST;
555 else if (y == st->height-1) p = SOUTH;
556 else if (x == 0) p = WEST;
561 ? &st->all_pieces[PIECE_A_HOLLOW].pieces[p]
562 : &st->all_pieces[PIECE_B_HOLLOW].pieces[p]);
565 ? &st->all_pieces[PIECE_A_FILLED].pieces[p]
566 : &st->all_pieces[PIECE_B_FILLED].pieces[p]);
571 draw_piece (struct state *st, int x, int y, int clear_p)
573 struct piece *hollow, *filled;
574 int from_x = st->state[y * st->width + x].x;
575 int from_y = st->state[y * st->width + x].y;
577 get_piece(st, x, y, &hollow, &filled);
579 XSetClipMask(st->dpy, st->gc, filled->pixmap);
580 XSetClipOrigin(st->dpy, st->gc,
581 st->x_border + (x * st->piece_width) - filled->x - 1,
582 st->y_border + (y * st->piece_width) - filled->y - 1);
586 XSetForeground(st->dpy, st->gc, st->bg);
587 XFillRectangle(st->dpy, st->window, st->gc,
588 st->x_border + (x * st->piece_width) -st->piece_width/2,
589 st->y_border + (y * st->piece_height) -st->piece_height/2,
590 st->piece_width*2, st->piece_height*2);
593 XCopyArea(st->dpy, st->source, st->window, st->gc,
594 st->x_border + (from_x * st->piece_width) - st->piece_width/2,
595 st->y_border + (from_y * st->piece_height) - st->piece_height/2,
596 st->piece_width*2, st->piece_height*2,
597 st->x_border + (x * st->piece_width) - st->piece_width/2,
598 st->y_border + (y * st->piece_height) - st->piece_height/2);
603 XSetForeground(st->dpy, st->gc, st->fg);
604 XSetClipMask(st->dpy, st->gc, hollow->pixmap);
605 XSetClipOrigin(st->dpy, st->gc,
606 st->x_border + (x * st->piece_width) - hollow->x - 1,
607 st->y_border + (y * st->piece_width) - hollow->y - 1);
608 XFillRectangle(st->dpy, st->window, st->gc,
609 st->x_border + (x * st->piece_width) - st->piece_width/2,
610 st->y_border + (y * st->piece_height) - st->piece_height/2,
611 st->piece_width*2, st->piece_height*2);
616 animate_swap (struct state *st, struct swap_state *sw)
620 if (sw->flashing > 1)
622 draw_piece(st, sw->x1, sw->y1, sw->flashing & 1);
623 draw_piece(st, sw->x2, sw->y2, sw->flashing & 1);
628 swap = st->state[sw->y1 * st->width + sw->x1];
629 st->state[sw->y1 * st->width + sw->x1] =
630 st->state[sw->y2 * st->width + sw->x2];
631 st->state[sw->y2 * st->width + sw->x2] = swap;
635 draw_piece(st, sw->x1, sw->y1, 0);
636 draw_piece(st, sw->x2, sw->y2, 0);
645 swap_pieces (struct state *st,
646 int src_x, int src_y, int dst_x, int dst_y,
649 struct swap_state *sw = &st->swap;
657 /* if animating, plan to flash the pieces on and off a few times */
658 sw->flashing = sw->draw_p ? 7 : 0;
660 return animate_swap(st, sw);
665 done (struct state *st)
668 for (y = 0; y < st->height; y++)
669 for (x = 0; x < st->width; x++)
671 int x2 = st->state[y * st->width + x].x;
672 int y2 = st->state[y * st->width + x].y;
673 if (x != x2 || y != y2)
681 shuffle (struct state *st, Bool draw_p)
683 struct piece *p1, *p2;
684 int src_x, src_y, dst_x = -1, dst_y = -1;
688 src_x = random() % st->width;
689 src_y = random() % st->height;
691 get_piece(st, src_x, src_y, &p1, 0);
693 /* Pick random coordinates until we find one that has the same kind of
694 piece as the first one we picked. Note that it's possible for there
695 to be only one piece of a particular shape on the board (this always
696 happens with the four corner pieces.)
700 dst_x = random() % st->width;
701 dst_y = random() % st->height;
702 get_piece(st, dst_x, dst_y, &p2, 0);
705 if (src_x == dst_x && src_y == dst_y)
708 return swap_pieces(st, src_x, src_y, dst_x, dst_y, draw_p);
713 shuffle_all (struct state *st)
716 for (j = 0; j < 5; j++) {
717 /* swap each piece with another 5x */
718 int i = (st->width * st->height * 5);
722 /* and do that whole process up to 5x if we ended up with a solved
723 board (this often happens with 4x4 boards.) */
731 unshuffle (struct state *st)
734 for (i = 0; i < st->width * st->height * 4; i++)
736 int x = random() % st->width;
737 int y = random() % st->height;
738 int x2 = st->state[y * st->width + x].x;
739 int y2 = st->state[y * st->width + x].y;
740 if (x != x2 || y != y2)
742 return swap_pieces(st, x, y, x2, y2, True);
750 animate_clear (struct state *st)
752 while (st->clearing > 0)
754 int x = random() % st->width;
755 int y = random() % st->height;
756 XPoint *p = &st->state[y * st->width + x];
759 draw_piece(st, p->x, p->y, 2);
769 clear_all (struct state *st)
771 st->clearing = st->width * st->height;
772 return animate_clear(st);
777 jigsaw_init (Display *dpy, Window window)
779 struct state *st = (struct state *) calloc (1, sizeof(*st));
782 st->delay = get_integer_resource (st->dpy, "delay", "Integer");
783 st->delay2 = get_integer_resource (st->dpy, "delay2", "Integer") * 1000000;
784 st->border_width = get_integer_resource (st->dpy, "pieceBorderWidth",
786 if (st->delay == 0) st->delay = 1; /* kludge */
792 jigsaw_draw (Display *dpy, Window window, void *closure)
794 struct state *st = (struct state *) closure;
798 if (st->img_loader) /* still loading */
800 st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
801 if (! st->img_loader) { /* just finished */
803 for (y = 0; y < st->height; y++)
804 for (x = 0; x < st->width; x++)
805 draw_piece(st, x, y, 0);
810 if (st->swap.flashing)
811 delay = animate_swap (st, &st->swap);
812 else if (st->clearing)
813 delay = animate_clear (st);
816 if (st->jigstate == 0)
821 else if (st->jigstate == 1)
830 delay = unshuffle(st);
833 else if (st->jigstate == 2)
836 delay = clear_all(st);
842 if (delay == 1) delay = 0; /* kludge */
843 return (delay ? delay : st->delay * 10);
847 jigsaw_reshape (Display *dpy, Window window, void *closure,
848 unsigned int w, unsigned int h)
850 /* window size is checked each time a new puzzle begins */
854 jigsaw_event (Display *dpy, Window window, void *closure, XEvent *event)
860 jigsaw_free (Display *dpy, Window window, void *closure)
862 struct state *st = (struct state *) closure;
863 free_puzzle_pixmaps (st);
864 if (st->state) free (st->state);
865 if (st->gc) XFreeGC (dpy, st->gc);
866 if (st->source) XFreePixmap (dpy, st->source);
872 static const char *jigsaw_defaults [] = {
873 ".background: Black",
874 ".foreground: #AAAAAA",
877 "*pieceBorderWidth: -1",
878 #ifdef __sgi /* really, HAVE_READ_DISPLAY_EXTENSION */
884 static XrmOptionDescRec jigsaw_options [] = {
885 { "-delay", ".delay", XrmoptionSepArg, 0 },
886 { "-delay2", ".delay2", XrmoptionSepArg, 0 },
887 { "-bw", ".pieceBorderWidth", XrmoptionSepArg, 0 },
888 { "-border-width", ".pieceBorderWidth", XrmoptionSepArg, 0 },
893 XSCREENSAVER_MODULE ("Jigsaw", jigsaw)