1
/* xscreensaver, Copyright (c) 1997-2008 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)
228
int w = (size ? size * 3 : 2);
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 = (st->piece_width ? xgwa.width / st->piece_width : 0);
408
st->height = (st->piece_height ? xgwa.height / st->piece_height : 0);
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",
878
"*pieceBorderWidth: -1",
879
#ifdef __sgi /* really, HAVE_READ_DISPLAY_EXTENSION */
885
static XrmOptionDescRec jigsaw_options [] = {
886
{ "-delay", ".delay", XrmoptionSepArg, 0 },
887
{ "-delay2", ".delay2", XrmoptionSepArg, 0 },
888
{ "-bw", ".pieceBorderWidth", XrmoptionSepArg, 0 },
889
{ "-border-width", ".pieceBorderWidth", XrmoptionSepArg, 0 },
894
XSCREENSAVER_MODULE ("Jigsaw", jigsaw)