http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.00.tar.gz
[xscreensaver] / hacks / jigsaw.c
1 /* xscreensaver, Copyright (c) 1997, 1998, 2001 Jamie Zawinski <jwz@jwz.org>
2  *
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 
9  * implied warranty.
10  */
11
12 /*
13   TODO:
14
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.
20
21     =  Rotate the pieces as well, so that we can swap the corner
22        and edge pieces with each other.
23
24     =  The shapes of the piece bitmaps still aren't quite right.
25        They should line up with no overlap.  They don't...
26     
27     =  Have it drop all pieces to the "floor" then pick them up to
28        reassemble the picture.
29
30     =  As a joke, maybe sometimes have one piece that doesn't fit?
31        Or lose a piece?
32  */
33
34 #include "screenhack.h"
35
36 #define DEBUG
37
38 #include "images/jigsaw/jigsaw_a_h.xbm"
39 #include "images/jigsaw/jigsaw_a_n_h.xbm"
40 #include "images/jigsaw/jigsaw_a_ne_h.xbm"
41 #include "images/jigsaw/jigsaw_a_e_h.xbm"
42 #include "images/jigsaw/jigsaw_a_se_h.xbm"
43 #include "images/jigsaw/jigsaw_a_s_h.xbm"
44 #include "images/jigsaw/jigsaw_a_sw_h.xbm"
45 #include "images/jigsaw/jigsaw_a_w_h.xbm"
46 #include "images/jigsaw/jigsaw_a_nw_h.xbm"
47
48 #include "images/jigsaw/jigsaw_b_h.xbm"
49 #include "images/jigsaw/jigsaw_b_n_h.xbm"
50 #include "images/jigsaw/jigsaw_b_ne_h.xbm"
51 #include "images/jigsaw/jigsaw_b_e_h.xbm"
52 #include "images/jigsaw/jigsaw_b_se_h.xbm"
53 #include "images/jigsaw/jigsaw_b_s_h.xbm"
54 #include "images/jigsaw/jigsaw_b_sw_h.xbm"
55 #include "images/jigsaw/jigsaw_b_w_h.xbm"
56 #include "images/jigsaw/jigsaw_b_nw_h.xbm"
57
58 #include "images/jigsaw/jigsaw_a_f.xbm"
59 #include "images/jigsaw/jigsaw_a_n_f.xbm"
60 #include "images/jigsaw/jigsaw_a_ne_f.xbm"
61 #include "images/jigsaw/jigsaw_a_e_f.xbm"
62 #include "images/jigsaw/jigsaw_a_se_f.xbm"
63 #include "images/jigsaw/jigsaw_a_s_f.xbm"
64 #include "images/jigsaw/jigsaw_a_sw_f.xbm"
65 #include "images/jigsaw/jigsaw_a_w_f.xbm"
66 #include "images/jigsaw/jigsaw_a_nw_f.xbm"
67
68 #include "images/jigsaw/jigsaw_b_f.xbm"
69 #include "images/jigsaw/jigsaw_b_n_f.xbm"
70 #include "images/jigsaw/jigsaw_b_ne_f.xbm"
71 #include "images/jigsaw/jigsaw_b_e_f.xbm"
72 #include "images/jigsaw/jigsaw_b_se_f.xbm"
73 #include "images/jigsaw/jigsaw_b_s_f.xbm"
74 #include "images/jigsaw/jigsaw_b_sw_f.xbm"
75 #include "images/jigsaw/jigsaw_b_w_f.xbm"
76 #include "images/jigsaw/jigsaw_b_nw_f.xbm"
77
78 #define GRID_WIDTH  66
79 #define GRID_HEIGHT 66
80
81 #define CENTER    0
82 #define NORTH     1
83 #define NORTHEAST 2
84 #define EAST      3
85 #define SOUTHEAST 4
86 #define SOUTH     5
87 #define SOUTHWEST 6
88 #define WEST      7
89 #define NORTHWEST 8
90
91 struct piece {
92   int width, height;
93   int x, y;
94   Pixmap pixmap;
95 };
96
97 struct set {
98   struct piece pieces[9];
99 };
100
101 #define PIECE_A_HOLLOW 0
102 #define PIECE_A_FILLED 1
103 #define PIECE_B_HOLLOW 2
104 #define PIECE_B_FILLED 3
105
106 static struct set all_pieces[4];
107
108 static void
109 init_images(Display *dpy, Window window)
110 {
111 # define LOAD_PIECE(PIECE,NAME)                                 \
112     PIECE.x = jigsaw_##NAME##_x_hot;                            \
113     PIECE.y = jigsaw_##NAME##_y_hot;                            \
114     PIECE.pixmap =                                              \
115     XCreatePixmapFromBitmapData(dpy, window,                    \
116                                 (char *) jigsaw_##NAME##_bits,  \
117                                 jigsaw_##NAME##_width,          \
118                                 jigsaw_##NAME##_height,         \
119                                 1, 0, 1)
120
121 # define LOAD_PIECES(SET,PREFIX,SUFFIX)                         \
122     LOAD_PIECE(SET.pieces[CENTER],    PREFIX##_##SUFFIX);       \
123     LOAD_PIECE(SET.pieces[NORTH],     PREFIX##_n_##SUFFIX);     \
124     LOAD_PIECE(SET.pieces[NORTHEAST], PREFIX##_ne_##SUFFIX);    \
125     LOAD_PIECE(SET.pieces[EAST],      PREFIX##_e_##SUFFIX);     \
126     LOAD_PIECE(SET.pieces[SOUTHEAST], PREFIX##_se_##SUFFIX);    \
127     LOAD_PIECE(SET.pieces[SOUTH],     PREFIX##_s_##SUFFIX);     \
128     LOAD_PIECE(SET.pieces[SOUTHWEST], PREFIX##_sw_##SUFFIX);    \
129     LOAD_PIECE(SET.pieces[WEST],      PREFIX##_w_##SUFFIX);     \
130     LOAD_PIECE(SET.pieces[NORTHWEST], PREFIX##_nw_##SUFFIX)
131
132   LOAD_PIECES(all_pieces[PIECE_A_HOLLOW],a,h);
133   LOAD_PIECES(all_pieces[PIECE_A_FILLED],a,f);
134   LOAD_PIECES(all_pieces[PIECE_B_HOLLOW],b,h);
135   LOAD_PIECES(all_pieces[PIECE_B_FILLED],b,f);
136
137 # undef LOAD_PIECE
138 # undef LOAD_PIECES
139 }
140
141 static Pixmap
142 read_screen (Display *dpy, Window window, int *widthP, int *heightP)
143 {
144   Pixmap p;
145   XWindowAttributes xgwa;
146   XGCValues gcv;
147   GC gc;
148   XGetWindowAttributes (dpy, window, &xgwa);
149   *widthP = xgwa.width;
150   *heightP = xgwa.height;
151
152   XClearWindow(dpy, window);
153   grab_screen_image(xgwa.screen, window);
154   p = XCreatePixmap(dpy, window, *widthP, *heightP, xgwa.depth);
155   gcv.function = GXcopy;
156   gc = XCreateGC (dpy, window, GCFunction, &gcv);
157   XCopyArea (dpy, window, p, gc, 0, 0, *widthP, *heightP, 0, 0);
158
159   XFreeGC (dpy, gc);
160
161   return p;
162 }
163
164
165 static int width, height;
166 static int x_border, y_border;
167 static Pixmap source;
168 static GC gc;
169 static Bool tweak;
170 static int fg, bg;
171 static XPoint *state = 0;
172
173 static void
174 jigsaw_init(Display *dpy, Window window)
175 {
176   XWindowAttributes xgwa;
177   int x, y;
178   XGCValues gcv;
179   Colormap cmap;
180   int source_w, source_h;
181
182   tweak = random()&1;
183
184   source = read_screen (dpy, window, &source_w, &source_h);
185
186   XGetWindowAttributes (dpy, window, &xgwa);
187   cmap = xgwa.colormap;
188   width  = xgwa.width  / GRID_WIDTH;
189   height = xgwa.height / GRID_HEIGHT;
190   x_border = (xgwa.width  - (width  * GRID_WIDTH)) / 2;
191   y_border = (xgwa.height - (height * GRID_WIDTH)) / 2;
192
193   if (width < 4 || height < 4)
194     {
195       fprintf (stderr, "%s: window too small: %dx%d (need at least %dx%d)\n",
196                progname, xgwa.width, xgwa.height,
197                GRID_WIDTH * 4, GRID_HEIGHT * 4);
198       exit (1);
199     }
200
201   if (!state)
202     state = (XPoint *) malloc(width * height * sizeof(XPoint));
203   gc = XCreateGC (dpy, window, 0, &gcv);
204
205   {
206     XColor fgc, bgc;
207     char *fgs = get_string_resource("foreground", "Foreground");
208     char *bgs = get_string_resource("background", "Background");
209     Bool fg_ok, bg_ok;
210     if (!XParseColor (dpy, cmap, fgs, &fgc))
211       XParseColor (dpy, cmap, "gray", &fgc);
212     if (!XParseColor (dpy, cmap, bgs, &bgc))
213       XParseColor (dpy, cmap, "black", &bgc);
214
215     fg_ok = XAllocColor (dpy, cmap, &fgc);
216     bg_ok = XAllocColor (dpy, cmap, &bgc);
217
218     /* If we weren't able to allocate the two colors we want from the
219        colormap (which is likely if the screen has been grabbed on an
220        8-bit SGI visual -- don't ask) then just go through the map
221        and find the closest color to the ones we wanted, and use those
222        pixels without actually allocating them.
223      */
224     if (fg_ok)
225       fg = fgc.pixel;
226     else
227       fg = 0;
228
229     if (bg_ok)
230       bg = bgc.pixel;
231     else
232       bg = 1;
233
234     if (!fg_ok || bg_ok)
235       {
236         int i;
237         unsigned long fgd = ~0;
238         unsigned long bgd = ~0;
239         int max = visual_cells (xgwa.screen, xgwa.visual);
240         XColor *all = (XColor *) calloc(sizeof (*all), max);
241         for (i = 0; i < max; i++)
242           {
243             all[i].flags = DoRed|DoGreen|DoBlue;
244             all[i].pixel = i;
245           }
246         XQueryColors (dpy, cmap, all, max);
247         for(i = 0; i < max; i++)
248           {
249             long rd, gd, bd;
250             unsigned long d;
251             if (!fg_ok)
252               {
253                 rd = (all[i].red   >> 8) - (fgc.red   >> 8);
254                 gd = (all[i].green >> 8) - (fgc.green >> 8);
255                 bd = (all[i].blue  >> 8) - (fgc.blue  >> 8);
256                 if (rd < 0) rd = -rd;
257                 if (gd < 0) gd = -gd;
258                 if (bd < 0) bd = -bd;
259                 d = (rd << 1) + (gd << 2) + bd;
260                 if (d < fgd)
261                   {
262                     fgd = d;
263                     fg = all[i].pixel;
264                     if (d == 0)
265                       fg_ok = True;
266                   }
267               }
268
269             if (!bg_ok)
270               {
271                 rd = (all[i].red   >> 8) - (bgc.red   >> 8);
272                 gd = (all[i].green >> 8) - (bgc.green >> 8);
273                 bd = (all[i].blue  >> 8) - (bgc.blue  >> 8);
274                 if (rd < 0) rd = -rd;
275                 if (gd < 0) gd = -gd;
276                 if (bd < 0) bd = -bd;
277                 d = (rd << 1) + (gd << 2) + bd;
278                 if (d < bgd)
279                   {
280                     bgd = d;
281                     bg = all[i].pixel;
282                     if (d == 0)
283                       bg_ok = True;
284                   }
285               }
286
287             if (fg_ok && bg_ok)
288               break;
289           }
290         XFree(all);
291       }
292   }
293
294   /* Reset the window's background color... */
295   XSetWindowBackground (dpy, window, bg);
296   XClearWindow(dpy, window);
297
298   for (y = 0; y < height; y++)
299     for (x = 0; x < width; x++)
300       {
301         state[y * width + x].x = x;
302         state[y * width + x].y = y;
303       }
304 }
305
306
307 static void
308 get_piece(int x, int y, struct piece **hollow, struct piece **filled)
309 {
310   int p;
311   Bool which = (x & 1) == (y & 1);
312
313   if      (x == 0       && y == 0)        p = NORTHWEST;
314   else if (x == width-1 && y == 0)        p = NORTHEAST;
315   else if (x == width-1 && y == height-1) p = SOUTHEAST;
316   else if (x == 0       && y == height-1) p = SOUTHWEST;
317   else if (y == 0)                        p = NORTH;
318   else if (x == width-1)                  p = EAST;
319   else if (y == height-1)                 p = SOUTH;
320   else if (x == 0)                        p = WEST;
321   else                                    p = CENTER;
322
323   if (tweak) which = !which;
324   if (hollow)
325     *hollow = (which
326                ? &all_pieces[PIECE_A_HOLLOW].pieces[p]
327                : &all_pieces[PIECE_B_HOLLOW].pieces[p]);
328   if (filled)
329     *filled = (which
330                ? &all_pieces[PIECE_A_FILLED].pieces[p]
331                : &all_pieces[PIECE_B_FILLED].pieces[p]);
332 }
333
334
335 static void
336 draw_piece(Display *dpy, Window window, int x, int y, int clear_p)
337 {
338   struct piece *hollow, *filled;
339   int from_x = state[y * width + x].x;
340   int from_y = state[y * width + x].y;
341
342   get_piece(x, y, &hollow, &filled);
343           
344   XSetClipMask(dpy, gc, filled->pixmap);
345   XSetClipOrigin(dpy, gc,
346                  x_border + (x * GRID_WIDTH) - filled->x - 1,
347                  y_border + (y * GRID_WIDTH) - filled->y - 1);
348
349   if (clear_p)
350     {
351       XSetForeground(dpy, gc, bg);
352       XFillRectangle(dpy, window, gc,
353                      x_border + (x * GRID_WIDTH)  - GRID_WIDTH/2,
354                      y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2,
355                      GRID_WIDTH*2, GRID_HEIGHT*2);
356     }
357   else
358     XCopyArea(dpy, source, window, gc,
359               x_border + (from_x * GRID_WIDTH)  - GRID_WIDTH/2,
360               y_border + (from_y * GRID_HEIGHT) - GRID_HEIGHT/2,
361               GRID_WIDTH*2, GRID_HEIGHT*2,
362               x_border + (x * GRID_WIDTH)  - GRID_WIDTH/2,
363               y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2);
364
365   if (clear_p > 1)
366     return;
367
368   XSetForeground(dpy, gc, fg);
369   XSetClipMask(dpy, gc, hollow->pixmap);
370   XSetClipOrigin(dpy, gc,
371                  x_border + (x * GRID_WIDTH) - hollow->x - 1,
372                  y_border + (y * GRID_WIDTH) - hollow->y - 1);
373   XFillRectangle(dpy, window, gc,
374                  x_border + (x * GRID_WIDTH)  - GRID_WIDTH/2,
375                  y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2,
376                  GRID_WIDTH*2, GRID_HEIGHT*2);
377
378   if (clear_p)
379     {
380       /* If the pieces lined up right, we could do this by just not drawing
381          the outline -- but that doesn't look right, since it eats the outlines
382          of the adjascent pieces.  So draw the outline, then chop off the outer
383          edge if this is a border piece.
384        */
385       XSetForeground(dpy, gc, bg);
386       if (x == 0)
387         XFillRectangle(dpy, window, gc,
388                        x_border - 2,
389                        y_border + (y * GRID_HEIGHT),
390                        3, GRID_HEIGHT);
391       else if (x == width-1)
392         XFillRectangle(dpy, window, gc,
393                        x_border + ((x+1) * GRID_WIDTH) - 2,
394                        y_border + (y * GRID_HEIGHT),
395                        3, GRID_HEIGHT);
396
397       if (y == 0)
398         XFillRectangle(dpy, window, gc,
399                        x_border + (x * GRID_WIDTH),
400                        y_border - 2,
401                        GRID_WIDTH, 3);
402       else if (y == height-1)
403         XFillRectangle(dpy, window, gc,
404                        x_border + (x * GRID_WIDTH),
405                        y_border + ((y+1) * GRID_HEIGHT) - 2,
406                        GRID_WIDTH, 3);
407     }
408 }
409
410
411 static void
412 swap_pieces(Display *dpy, Window window,
413             int src_x, int src_y, int dst_x, int dst_y,
414             Bool draw_p)
415 {
416   XPoint swap;
417   int i;
418   if (draw_p)
419     for (i = 0; i < 3; i++)
420       {
421         draw_piece(dpy, window, src_x, src_y, 1);
422         draw_piece(dpy, window, dst_x, dst_y, 1);
423         XSync(dpy, False);
424         usleep(50000);
425         draw_piece(dpy, window, src_x, src_y, 0);
426         draw_piece(dpy, window, dst_x, dst_y, 0);
427         XSync(dpy, False);
428         usleep(50000);
429       }
430
431   swap = state[src_y * width + src_x];
432   state[src_y * width + src_x] = state[dst_y * width + dst_x];
433   state[dst_y * width + dst_x] = swap;
434
435   if (draw_p)
436     {
437       draw_piece(dpy, window, src_x, src_y, 0);
438       draw_piece(dpy, window, dst_x, dst_y, 0);
439       XSync(dpy, False);
440     }
441 }
442
443
444 static void
445 shuffle(Display *dpy, Window window, Bool draw_p)
446 {
447   struct piece *p1, *p2;
448   int src_x, src_y, dst_x = -1, dst_y = -1;
449
450  AGAIN:
451   p1 = p2 = 0;
452   src_x = random() % width;
453   src_y = random() % height;
454
455   get_piece(src_x, src_y, &p1, 0);
456
457   /* Pick random coordinates until we find one that has the same kind of
458      piece as the first one we picked.  Note that it's possible for there
459      to be only one piece of a particular shape on the board (this commonly
460      happens with the corner pieces.)
461    */
462   while (p1 != p2)
463     {
464       dst_x = random() % width;
465       dst_y = random() % height;
466       get_piece(dst_x, dst_y, &p2, 0);
467     }
468
469   if (src_x == dst_x && src_y == dst_y)
470     goto AGAIN;
471
472   swap_pieces(dpy, window, src_x, src_y, dst_x, dst_y, draw_p);
473 }
474
475
476 static void
477 shuffle_all(Display *dpy, Window window)
478 {
479   int i = (width * height * 10);
480   while (i > 0)
481     {
482       shuffle(dpy, window, False);
483       i--;
484     }
485 }
486
487 static void
488 unshuffle(Display *dpy, Window window)
489 {
490   int i;
491   for (i = 0; i < width * height * 4; i++)
492     {
493       int x = random() % width;
494       int y = random() % height;
495       int x2 = state[y * width + x].x;
496       int y2 = state[y * width + x].y;
497       if (x != x2 || y != y2)
498         {
499           swap_pieces(dpy, window, x, y, x2, y2, True);
500           break;
501         }
502     }
503 }
504
505 static void
506 clear_all(Display *dpy, Window window)
507 {
508   int n = width * height;
509   while (n > 0)
510     {
511       int x = random() % width;
512       int y = random() % height;
513       XPoint *p = &state[y * width + x];
514       if (p->x == -1)
515         continue;
516       draw_piece(dpy, window, p->x, p->y, 2);
517       XSync(dpy, False);
518       usleep(1000);
519       p->x = p->y = -1;
520       n--;
521     }
522 }
523
524 static Bool
525 done(void)
526 {
527   int x, y;
528   for (y = 0; y < height; y++)
529     for (x = 0; x < width; x++)
530       {
531         int x2 = state[y * width + x].x;
532         int y2 = state[y * width + x].y;
533         if (x != x2 || y != y2)
534           return False;
535       }
536   return True;
537 }
538
539
540 \f
541 char *progclass = "Jigsaw";
542
543 char *defaults [] = {
544   ".background:         Black",
545   ".foreground:         Gray40",
546   "*delay:              70000",
547   "*delay2:             5",
548 #ifdef __sgi    /* really, HAVE_READ_DISPLAY_EXTENSION */
549   "*visualID:           Best",
550 #endif
551   0
552 };
553
554 XrmOptionDescRec options [] = {
555   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
556   { "-delay2",          ".delay2",              XrmoptionSepArg, 0 },
557   { 0, 0, 0, 0 }
558 };
559
560 void
561 screenhack (Display *dpy, Window window)
562 {
563   int delay = get_integer_resource("delay", "Integer");
564   int delay2 = get_integer_resource("delay2", "Integer");
565
566   init_images(dpy, window);
567
568   while (1)
569     {
570       int x, y;
571       jigsaw_init (dpy, window);
572       shuffle_all(dpy, window);
573
574       for (y = 0; y < height; y++)
575         for (x = 0; x < width; x++)
576           draw_piece(dpy, window, x, y, 0);
577
578       while (!done())
579         {
580           unshuffle(dpy, window);
581           XSync (dpy, False);
582           screenhack_handle_events (dpy);
583           if (delay) usleep (delay);
584         }
585
586       screenhack_handle_events (dpy);
587       if (delay2)
588         usleep (delay2 * 1000000);
589
590       clear_all(dpy, window);
591     }
592 }