f16ccaa2452b2b20dda74bb6fc9ecdf1ac4684d9
[xscreensaver] / hacks / puzzle.c
1 /* xscreensaver, Copyright (c) 1997 Jamie Zawinski <jwz@netscape.com>
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 "pieces/puzzle_a_h.xbm"
39 #include "pieces/puzzle_a_n_h.xbm"
40 #include "pieces/puzzle_a_ne_h.xbm"
41 #include "pieces/puzzle_a_e_h.xbm"
42 #include "pieces/puzzle_a_se_h.xbm"
43 #include "pieces/puzzle_a_s_h.xbm"
44 #include "pieces/puzzle_a_sw_h.xbm"
45 #include "pieces/puzzle_a_w_h.xbm"
46 #include "pieces/puzzle_a_nw_h.xbm"
47
48 #include "pieces/puzzle_b_h.xbm"
49 #include "pieces/puzzle_b_n_h.xbm"
50 #include "pieces/puzzle_b_ne_h.xbm"
51 #include "pieces/puzzle_b_e_h.xbm"
52 #include "pieces/puzzle_b_se_h.xbm"
53 #include "pieces/puzzle_b_s_h.xbm"
54 #include "pieces/puzzle_b_sw_h.xbm"
55 #include "pieces/puzzle_b_w_h.xbm"
56 #include "pieces/puzzle_b_nw_h.xbm"
57
58 #include "pieces/puzzle_a_f.xbm"
59 #include "pieces/puzzle_a_n_f.xbm"
60 #include "pieces/puzzle_a_ne_f.xbm"
61 #include "pieces/puzzle_a_e_f.xbm"
62 #include "pieces/puzzle_a_se_f.xbm"
63 #include "pieces/puzzle_a_s_f.xbm"
64 #include "pieces/puzzle_a_sw_f.xbm"
65 #include "pieces/puzzle_a_w_f.xbm"
66 #include "pieces/puzzle_a_nw_f.xbm"
67
68 #include "pieces/puzzle_b_f.xbm"
69 #include "pieces/puzzle_b_n_f.xbm"
70 #include "pieces/puzzle_b_ne_f.xbm"
71 #include "pieces/puzzle_b_e_f.xbm"
72 #include "pieces/puzzle_b_se_f.xbm"
73 #include "pieces/puzzle_b_s_f.xbm"
74 #include "pieces/puzzle_b_sw_f.xbm"
75 #include "pieces/puzzle_b_w_f.xbm"
76 #include "pieces/puzzle_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 = puzzle_##NAME##_x_hot;                            \
113     PIECE.y = puzzle_##NAME##_y_hot;                            \
114     PIECE.pixmap =                                              \
115     XCreatePixmapFromBitmapData(dpy, window,                    \
116                                 (char *) puzzle_##NAME##_bits,  \
117                                 puzzle_##NAME##_width,          \
118                                 puzzle_##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 puzzle_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 (!state)
194     state = (XPoint *) malloc(width * height * sizeof(XPoint));
195   gc = XCreateGC (dpy, window, 0, &gcv);
196
197   {
198     XColor fgc, bgc;
199     char *fgs = get_string_resource("foreground", "Foreground");
200     char *bgs = get_string_resource("background", "Background");
201     Bool fg_ok, bg_ok;
202     if (!XParseColor (dpy, cmap, fgs, &fgc))
203       XParseColor (dpy, cmap, "gray", &fgc);
204     if (!XParseColor (dpy, cmap, bgs, &bgc))
205       XParseColor (dpy, cmap, "black", &bgc);
206
207     fg_ok = XAllocColor (dpy, cmap, &fgc);
208     bg_ok = XAllocColor (dpy, cmap, &bgc);
209
210     /* If we weren't able to allocate the two colors we want from the
211        colormap (which is likely if the screen has been grabbed on an
212        8-bit SGI visual -- don't ask) then just go through the map
213        and find the closest color to the ones we wanted, and use those
214        pixels without actually allocating them.
215      */
216     if (fg_ok)
217       fg = fgc.pixel;
218     else
219       fg = 0;
220
221     if (bg_ok)
222       bg = bgc.pixel;
223     else
224       bg = 1;
225
226     if (!fg_ok || bg_ok)
227       {
228         int i;
229         unsigned long fgd = ~0;
230         unsigned long bgd = ~0;
231         int max = visual_cells (xgwa.screen, xgwa.visual);
232         XColor *all = (XColor *) calloc(sizeof (*all), max);
233         for (i = 0; i < max; i++)
234           {
235             all[i].flags = DoRed|DoGreen|DoBlue;
236             all[i].pixel = i;
237           }
238         XQueryColors (dpy, cmap, all, max);
239         for(i = 0; i < max; i++)
240           {
241             long rd, gd, bd;
242             unsigned long d;
243             if (!fg_ok)
244               {
245                 rd = (all[i].red   >> 8) - (fgc.red   >> 8);
246                 gd = (all[i].green >> 8) - (fgc.green >> 8);
247                 bd = (all[i].blue  >> 8) - (fgc.blue  >> 8);
248                 if (rd < 0) rd = -rd;
249                 if (gd < 0) gd = -gd;
250                 if (bd < 0) bd = -bd;
251                 d = (rd << 1) + (gd << 2) + bd;
252                 if (d < fgd)
253                   {
254                     fgd = d;
255                     fg = all[i].pixel;
256                     if (d == 0)
257                       fg_ok = True;
258                   }
259               }
260
261             if (!bg_ok)
262               {
263                 rd = (all[i].red   >> 8) - (bgc.red   >> 8);
264                 gd = (all[i].green >> 8) - (bgc.green >> 8);
265                 bd = (all[i].blue  >> 8) - (bgc.blue  >> 8);
266                 if (rd < 0) rd = -rd;
267                 if (gd < 0) gd = -gd;
268                 if (bd < 0) bd = -bd;
269                 d = (rd << 1) + (gd << 2) + bd;
270                 if (d < bgd)
271                   {
272                     bgd = d;
273                     bg = all[i].pixel;
274                     if (d == 0)
275                       bg_ok = True;
276                   }
277               }
278
279             if (fg_ok && bg_ok)
280               break;
281           }
282         XFree(all);
283       }
284   }
285
286   /* Reset the window's background color... */
287   XSetWindowBackground (dpy, window, bg);
288   XClearWindow(dpy, window);
289
290   for (y = 0; y < height; y++)
291     for (x = 0; x < width; x++)
292       {
293         state[y * width + x].x = x;
294         state[y * width + x].y = y;
295       }
296 }
297
298
299 static void
300 get_piece(int x, int y, struct piece **hollow, struct piece **filled)
301 {
302   int p;
303   Bool which = (x & 1) == (y & 1);
304
305   if      (x == 0       && y == 0)        p = NORTHWEST;
306   else if (x == width-1 && y == 0)        p = NORTHEAST;
307   else if (x == width-1 && y == height-1) p = SOUTHEAST;
308   else if (x == 0       && y == height-1) p = SOUTHWEST;
309   else if (y == 0)                        p = NORTH;
310   else if (x == width-1)                  p = EAST;
311   else if (y == height-1)                 p = SOUTH;
312   else if (x == 0)                        p = WEST;
313   else                                    p = CENTER;
314
315   if (tweak) which = !which;
316   if (hollow)
317     *hollow = (which
318                ? &all_pieces[PIECE_A_HOLLOW].pieces[p]
319                : &all_pieces[PIECE_B_HOLLOW].pieces[p]);
320   if (filled)
321     *filled = (which
322                ? &all_pieces[PIECE_A_FILLED].pieces[p]
323                : &all_pieces[PIECE_B_FILLED].pieces[p]);
324 }
325
326
327 static void
328 draw_piece(Display *dpy, Window window, int x, int y, int clear_p)
329 {
330   struct piece *hollow, *filled;
331   int from_x = state[y * width + x].x;
332   int from_y = state[y * width + x].y;
333
334   get_piece(x, y, &hollow, &filled);
335           
336   XSetClipMask(dpy, gc, filled->pixmap);
337   XSetClipOrigin(dpy, gc,
338                  x_border + (x * GRID_WIDTH) - filled->x - 1,
339                  y_border + (y * GRID_WIDTH) - filled->y - 1);
340
341   if (clear_p)
342     {
343       XSetForeground(dpy, gc, bg);
344       XFillRectangle(dpy, window, gc,
345                      x_border + (x * GRID_WIDTH)  - GRID_WIDTH/2,
346                      y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2,
347                      GRID_WIDTH*2, GRID_HEIGHT*2);
348     }
349   else
350     XCopyArea(dpy, source, window, gc,
351               x_border + (from_x * GRID_WIDTH)  - GRID_WIDTH/2,
352               y_border + (from_y * GRID_HEIGHT) - GRID_HEIGHT/2,
353               GRID_WIDTH*2, GRID_HEIGHT*2,
354               x_border + (x * GRID_WIDTH)  - GRID_WIDTH/2,
355               y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2);
356
357   if (clear_p > 1)
358     return;
359
360   XSetForeground(dpy, gc, fg);
361   XSetClipMask(dpy, gc, hollow->pixmap);
362   XSetClipOrigin(dpy, gc,
363                  x_border + (x * GRID_WIDTH) - hollow->x - 1,
364                  y_border + (y * GRID_WIDTH) - hollow->y - 1);
365   XFillRectangle(dpy, window, gc,
366                  x_border + (x * GRID_WIDTH)  - GRID_WIDTH/2,
367                  y_border + (y * GRID_HEIGHT) - GRID_HEIGHT/2,
368                  GRID_WIDTH*2, GRID_HEIGHT*2);
369
370   if (clear_p)
371     {
372       /* If the pieces lined up right, we could do this by just not drawing
373          the outline -- but that doesn't look right, since it eats the outlines
374          of the adjascent pieces.  So draw the outline, then chop off the outer
375          edge if this is a border piece.
376        */
377       XSetForeground(dpy, gc, bg);
378       if (x == 0)
379         XFillRectangle(dpy, window, gc,
380                        x_border - 2,
381                        y_border + (y * GRID_HEIGHT),
382                        3, GRID_HEIGHT);
383       else if (x == width-1)
384         XFillRectangle(dpy, window, gc,
385                        x_border + ((x+1) * GRID_WIDTH) - 2,
386                        y_border + (y * GRID_HEIGHT),
387                        3, GRID_HEIGHT);
388
389       if (y == 0)
390         XFillRectangle(dpy, window, gc,
391                        x_border + (x * GRID_WIDTH),
392                        y_border - 2,
393                        GRID_WIDTH, 3);
394       else if (y == height-1)
395         XFillRectangle(dpy, window, gc,
396                        x_border + (x * GRID_WIDTH),
397                        y_border + ((y+1) * GRID_HEIGHT) - 2,
398                        GRID_WIDTH, 3);
399     }
400 }
401
402
403 static void
404 swap_pieces(Display *dpy, Window window,
405             int src_x, int src_y, int dst_x, int dst_y,
406             Bool draw_p)
407 {
408   XPoint swap;
409   int i;
410   if (draw_p)
411     for (i = 0; i < 3; i++)
412       {
413         draw_piece(dpy, window, src_x, src_y, 1);
414         draw_piece(dpy, window, dst_x, dst_y, 1);
415         XSync(dpy, False);
416         usleep(50000);
417         draw_piece(dpy, window, src_x, src_y, 0);
418         draw_piece(dpy, window, dst_x, dst_y, 0);
419         XSync(dpy, False);
420         usleep(50000);
421       }
422
423   swap = state[src_y * width + src_x];
424   state[src_y * width + src_x] = state[dst_y * width + dst_x];
425   state[dst_y * width + dst_x] = swap;
426
427   if (draw_p)
428     {
429       draw_piece(dpy, window, src_x, src_y, 0);
430       draw_piece(dpy, window, dst_x, dst_y, 0);
431       XSync(dpy, False);
432     }
433 }
434
435
436 static void
437 shuffle(Display *dpy, Window window, Bool draw_p)
438 {
439   struct piece *p1, *p2;
440   int src_x, src_y, dst_x = -1, dst_y = -1;
441
442  AGAIN:
443   p1 = p2 = 0;
444   src_x = random() % width;
445   src_y = random() % height;
446
447   get_piece(src_x, src_y, &p1, 0);
448
449   /* Pick random coordinates until we find one that has the same kind of
450      piece as the first one we picked.  Note that it's possible for there
451      to be only one piece of a particular shape on the board (this commonly
452      happens with the corner pieces.)
453    */
454   while (p1 != p2)
455     {
456       dst_x = random() % width;
457       dst_y = random() % height;
458       get_piece(dst_x, dst_y, &p2, 0);
459     }
460
461   if (src_x == dst_x && src_y == dst_y)
462     goto AGAIN;
463
464   swap_pieces(dpy, window, src_x, src_y, dst_x, dst_y, draw_p);
465 }
466
467
468 static void
469 shuffle_all(Display *dpy, Window window)
470 {
471   int i = (width * height * 10);
472   while (i > 0)
473     {
474       shuffle(dpy, window, False);
475       i--;
476     }
477 }
478
479 static void
480 unshuffle(Display *dpy, Window window)
481 {
482   int i;
483   for (i = 0; i < width * height * 4; i++)
484     {
485       int x = random() % width;
486       int y = random() % height;
487       int x2 = state[y * width + x].x;
488       int y2 = state[y * width + x].y;
489       if (x != x2 || y != y2)
490         {
491           swap_pieces(dpy, window, x, y, x2, y2, True);
492           break;
493         }
494     }
495 }
496
497 static void
498 clear_all(Display *dpy, Window window)
499 {
500   int n = width * height;
501   while (n > 0)
502     {
503       int x = random() % width;
504       int y = random() % height;
505       XPoint *p = &state[y * width + x];
506       if (p->x == -1)
507         continue;
508       draw_piece(dpy, window, p->x, p->y, 2);
509       XSync(dpy, False);
510       usleep(1000);
511       p->x = p->y = -1;
512       n--;
513     }
514 }
515
516 static Bool
517 done(void)
518 {
519   int x, y;
520   for (y = 0; y < height; y++)
521     for (x = 0; x < width; x++)
522       {
523         int x2 = state[y * width + x].x;
524         int y2 = state[y * width + x].y;
525         if (x != x2 || y != y2)
526           return False;
527       }
528   return True;
529 }
530
531
532 \f
533 char *progclass = "Puzzle";
534
535 char *defaults [] = {
536   "Puzzle.background:   Black",         /* to placate SGI */
537   "Puzzle.foreground:   Gray40",
538   "*delay:              70000",
539   "*delay2:             5",
540   0
541 };
542
543 XrmOptionDescRec options [] = {
544   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
545   { "-delay2",          ".delay2",              XrmoptionSepArg, 0 },
546   { 0, 0, 0, 0 }
547 };
548
549 void
550 screenhack (Display *dpy, Window window)
551 {
552   int delay = get_integer_resource("delay", "Integer");
553   int delay2 = get_integer_resource("delay2", "Integer");
554
555   init_images(dpy, window);
556
557   while (1)
558     {
559       int x, y;
560       puzzle_init (dpy, window);
561       shuffle_all(dpy, window);
562
563       for (y = 0; y < height; y++)
564         for (x = 0; x < width; x++)
565           draw_piece(dpy, window, x, y, 0);
566
567       while (!done())
568         {
569           unshuffle(dpy, window);
570           XSync (dpy, True);
571           if (delay) usleep (delay);
572         }
573
574       if (delay2)
575         usleep (delay2 * 1000000);
576
577       clear_all(dpy, window);
578     }
579 }