From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / hacks / munch.c
1 /* Munching Squares and Mismunch
2  *
3  * Portions copyright 1992-2014 Jamie Zawinski <jwz@jwz.org>
4  *
5  *   Permission to use, copy, modify, distribute, and sell this
6  *   software and its documentation for any purpose is hereby
7  *   granted without fee, provided that the above copyright notice
8  *   appear in all copies and that both that copyright notice and
9  *   this permission notice appear in supporting documentation.  No
10  *   representations are made about the suitability of this software
11  *   for any purpose.  It is provided "as is" without express or
12  *   implied warranty.
13  *
14  * Portions Copyright 1997, Tim Showalter
15  *
16  *   Permission is granted to copy, modify, and use this as long
17  *   as this notice remains intact.  No warranties are expressed or
18  *   implied.  CMU Sucks.
19  * 
20  * Portions Copyright 2004 Steven Hazel <sah@thalassocracy.org>
21  *
22  *   (The "mismunch" part).
23  * 
24  * "munch.c" and "mismunch.c" merged by jwz, 29-Aug-2008.
25  *
26  *
27  *
28  ***********************************************************************
29  *
30  * HAKMEM
31  *
32  * MIT AI Memo 239, Feb. 29, 1972.
33  * Beeler, M., Gosper, R.W., and Schroeppel, R.
34  *
35  * http://www.inwap.com/pdp10/hbaker/hakmem/hacks.html#item146
36  *
37  ***********************************************************************
38  *
39  * ITEM 146: MUNCHING SQUARES
40  *
41  *     Another simple display program. It is thought that this was
42  *     discovered by Jackson Wright on the RLE PDP-1 circa 1962.
43  *
44  *          DATAI 2
45  *          ADDB 1,2
46  *          ROTC 2,-22
47  *          XOR 1,2
48  *          JRST .-4
49  *
50  *     2=X, 3=Y. Try things like 1001002 in data switches. This also
51  *     does * interesting things with operations other than XOR, and
52  *     rotations * other than -22. (Try IOR; AND; TSC; FADR; FDV(!);
53  *     ROT * -14, -9, -20, * ...)
54  *
55  * ITEM 147 (Schroeppel):
56  *
57  *     Munching squares is just views of the graph Y = X XOR T for
58  *     consecutive values of T = time.
59  *
60  * ITEM 147 (Cohen, Beeler):
61  *
62  *     A modification to munching squares which reveals them in frozen
63  *     states through opening and closing curtains: insert FADR 2,1
64  *     before the XOR. Try data switches =
65  *
66  *          4000,,4         1000,,2002      2000,,4        0,,1002
67  *
68  *     (Notation: <left half>,,<right half>)
69  *     Also try the FADR after the XOR, switches = 1001,,1.
70  *
71  ***********************************************************************
72  */
73
74 #include <math.h>
75 #include "screenhack.h"
76
77 typedef struct _muncher {
78   int mismunch;
79   int width;
80   int atX, atY;
81   int kX, kT, kY;
82   int grav;
83   XColor fgc;
84   int yshadow, xshadow;
85   int x, y, t;
86   int doom;
87   int done;
88 } muncher;
89
90
91 struct state {
92   Display *dpy;
93   Window window;
94
95   GC gc;
96   int delay, simul, clear, xor;
97   int logminwidth, logmaxwidth;
98   int restart, window_width, window_height;
99
100   int draw_n;  /* number of squares before we have to clear */
101   int draw_i;
102   int mismunch;
103
104   muncher **munchers;
105 };
106
107
108 /*
109  * dumb way to get # of digits in number.  Probably faster than actually
110  * doing a log and a division, maybe.
111  */
112 static int dumb_log_2(int k) 
113 {
114   int r = -1;
115   while (k > 0) {
116     k >>= 1; r++;
117   }
118   return r;
119 }
120
121
122 static void calc_logwidths (struct state *st) 
123 {
124   /* Choose a range of square sizes based on the window size.  We want
125      a power of 2 for the square width or the munch doesn't fill up.
126      Also, if a square doesn't fit inside an area 20% smaller than the
127      window, it's too big.  Mismunched squares that big make things
128      look too noisy. */
129
130   if (st->window_height < st->window_width &&
131       st->window_width < st->window_height * 5) {
132     st->logmaxwidth = (int)dumb_log_2(st->window_height * 0.8);
133   } else {
134     st->logmaxwidth = (int)dumb_log_2(st->window_width * 0.8);
135   }
136
137   if (st->logmaxwidth < 2) {
138     st->logmaxwidth = 2;
139   }
140
141   /* we always want three sizes of squares */
142   st->logminwidth = st->logmaxwidth - 2;
143
144   if (st->logminwidth < 2) {
145     st->logminwidth = 2;
146   }
147 }
148
149
150
151 static muncher *make_muncher (struct state *st) 
152 {
153   int logwidth;
154   XWindowAttributes xgwa;
155   muncher *m = (muncher *) malloc(sizeof(muncher));
156
157   XGetWindowAttributes(st->dpy, st->window, &xgwa);
158
159   m->mismunch = st->mismunch;
160
161   /* choose size -- power of two */
162   logwidth = (st->logminwidth +
163               (random() % (1 + st->logmaxwidth - st->logminwidth)));
164
165   m->width = 1 << logwidth;
166
167   /* draw at this location */
168   m->atX = (random() % (xgwa.width <= m->width ? 1
169                         : xgwa.width - m->width));
170   m->atY = (random() % (xgwa.height <= m->width ? 1
171                         : xgwa.height - m->width));
172
173   /* wrap-around by these values; no need to % as we end up doing that
174      later anyway */
175   m->kX = ((random() % 2)
176            ? (random() % m->width) : 0);
177   m->kT = ((random() % 2)
178            ? (random() % m->width) : 0);
179   m->kY = ((random() % 2)
180            ? (random() % m->width) : 0);
181
182   /* set the gravity of the munch, or rather, which direction we draw
183      stuff in. */
184   m->grav = random() % 2;
185
186   /* I like this color scheme better than random colors. */
187   switch (random() % 4) {
188     case 0:
189       m->fgc.red = random() % 65536;
190       m->fgc.blue = random() % 32768;
191       m->fgc.green = random() % 16384;
192       break;
193
194     case 1:
195       m->fgc.red = 0;
196       m->fgc.blue = random() % 65536;
197       m->fgc.green = random() % 16384;
198       break;
199
200     case 2:
201       m->fgc.red = random() % 8192;
202       m->fgc.blue = random() % 8192;
203       m->fgc.green = random() % 49152;
204       break;
205
206     case 3:
207       m->fgc.red = random() % 65536;
208       m->fgc.green = m->fgc.red;
209       m->fgc.blue = m->fgc.red;
210       break;
211   }
212
213   /* Sometimes draw a mostly-overlapping copy of the square.  This
214      generates all kinds of neat blocky graphics when drawing in xor
215      mode. */
216   if (!m->mismunch || (random() % 4)) {
217     m->xshadow = 0;
218     m->yshadow = 0;
219   } else {
220     m->xshadow = (random() % (m->width/3)) - (m->width/6);
221     m->yshadow = (random() % (m->width/3)) - (m->width/6);
222   }
223
224   /* Start with a random y value -- this sort of controls the type of
225      deformities seen in the squares. */
226   m->y = random() % 256;
227
228   m->t = 0;
229
230   /*
231     Doom each square to be aborted at some random point.
232     (When doom == (width - 1), the entire square will be drawn.)
233   */
234   m->doom = (m->mismunch ? (random() % m->width) : (m->width - 1));
235   m->done = 0;
236
237   return m;
238 }
239
240
241 static void munch (struct state *st, muncher *m) 
242 {
243   int drawX, drawY;
244   XWindowAttributes xgwa;
245
246   if (m->done) {
247     return;
248   }
249
250   XGetWindowAttributes(st->dpy, st->window, &xgwa);
251
252   if (!mono_p) {
253     /* XXX there are probably bugs with this. */
254     if (XAllocColor(st->dpy, xgwa.colormap, &m->fgc)) {
255       XSetForeground(st->dpy, st->gc, m->fgc.pixel);
256     }
257   }
258
259   /* Finally draw this pass of the munching error. */
260
261   for(m->x = 0; m->x < m->width; m->x++) {
262     /* figure out the next point */
263
264     /*
265       The ordinary Munching Squares calculation is:
266       m->y = ((m->x ^ ((m->t + m->kT) % m->width)) + m->kY) % m->width;
267
268       We create some feedback by plugging in y in place of x, and
269       make a couple of values negative so that some parts of some
270       squares get drawn in the wrong place.
271     */
272     if (m->mismunch)
273       m->y = ((-m->y ^ ((-m->t + m->kT) % m->width)) + m->kY) % m->width;
274     else
275       m->y = ((m->x ^ ((m->t + m->kT) % m->width)) + m->kY) % m->width;
276
277     drawX = ((m->x + m->kX) % m->width) + m->atX;
278     drawY = (m->grav ? m->y + m->atY : m->atY + m->width - 1 - m->y);
279
280     XDrawPoint(st->dpy, st->window, st->gc, drawX, drawY);
281     if ((m->xshadow != 0) || (m->yshadow != 0)) {
282       /* draw the corresponding shadow point */
283       XDrawPoint(st->dpy, st->window, st->gc, drawX + m->xshadow, drawY + m->yshadow);
284     }
285     /* XXX may want to change this to XDrawPoints,
286        but it's fast enough without it for the moment. */
287
288   }
289
290   m->t++;
291   if (m->t > m->doom) {
292     m->done = 1;
293   }
294 }
295
296
297 static void *
298 munch_init (Display *dpy, Window w)
299 {
300   struct state *st = (struct state *) calloc (1, sizeof(*st));
301   XWindowAttributes xgwa;
302   XGCValues gcv;
303   int i;
304   char *mm;
305
306   st->dpy = dpy;
307   st->window = w;
308   st->restart = 0;
309
310   /* get the dimensions of the window */
311   XGetWindowAttributes(st->dpy, w, &xgwa);
312
313   /* create the gc */
314   gcv.foreground= get_pixel_resource(st->dpy, xgwa.colormap,
315                                      "foreground","Foreground");
316   gcv.background= get_pixel_resource(st->dpy, xgwa.colormap,
317                                      "background","Background");
318
319   st->gc = XCreateGC(st->dpy, w, GCForeground|GCBackground, &gcv);
320
321   st->delay = get_integer_resource(st->dpy, "delay", "Integer");
322   if (st->delay < 0) st->delay = 0;
323
324   st->simul = get_integer_resource(st->dpy, "simul", "Integer");
325   if (st->simul < 1) st->simul = 1;
326
327   st->clear = get_integer_resource(st->dpy, "clear", "Integer");
328   if (st->clear < 0) st->clear = 0;
329
330   st->xor = get_boolean_resource(st->dpy, "xor", "Boolean");
331
332   mm = get_string_resource (st->dpy, "mismunch", "Mismunch");
333   if (!mm || !*mm || !strcmp(mm, "random"))
334     st->mismunch = random() & 1;
335   else
336     st->mismunch = get_boolean_resource (st->dpy, "mismunch", "Mismunch");
337
338   st->window_width = xgwa.width;
339   st->window_height = xgwa.height;
340
341   calc_logwidths(st);
342
343   /* always draw xor on mono. */
344   if (mono_p || st->xor) {
345     XSetFunction(st->dpy, st->gc, GXxor);
346   }
347
348   st->munchers = (muncher **) calloc(st->simul, sizeof(muncher *));
349   for (i = 0; i < st->simul; i++) {
350     st->munchers[i] = make_muncher(st);
351   }
352
353   return st;
354 }
355
356 static unsigned long
357 munch_draw (Display *dpy, Window w, void *closure)
358 {
359   struct state *st = (struct state *) closure;
360   int i;
361
362   for (i = 0; i < 5; i++) {
363
364   /* for (draw_i = 0; draw_i < simul; draw_i++)  */
365   {
366     munch(st, st->munchers[st->draw_i]);
367
368     if (st->munchers[st->draw_i]->done) {
369       st->draw_n++;
370
371       free(st->munchers[st->draw_i]);
372       st->munchers[st->draw_i] = make_muncher(st);
373     }
374   }
375
376   st->draw_i++;
377   if (st->draw_i >= st->simul) {
378     int i = 0;
379     st->draw_i = 0;
380     if (st->restart || (st->clear && st->draw_n >= st->clear)) {
381
382       char *mm = get_string_resource (st->dpy, "mismunch", "Mismunch");
383       if (!mm || !*mm || !strcmp(mm, "random"))
384         st->mismunch = random() & 1;
385
386       for (i = 0; i < st->simul; i++) {
387         free(st->munchers[i]);
388         st->munchers[i] = make_muncher(st);
389       }
390
391       XClearWindow(st->dpy, w);
392       st->draw_n = 0;
393       st->restart = 0;
394     }
395   }
396
397   }
398
399   return st->delay;
400 }
401
402
403 static void
404 munch_reshape (Display *dpy, Window window, void *closure, 
405                  unsigned int w, unsigned int h)
406 {
407   struct state *st = (struct state *) closure;
408   if (w != st->window_width ||
409       h != st->window_height) {
410     st->window_width = w;
411     st->window_height = h;
412     calc_logwidths(st);
413     st->restart = 1;
414     st->draw_i = 0;
415   }
416 }
417
418 static Bool
419 munch_event (Display *dpy, Window window, void *closure, XEvent *event)
420 {
421   struct state *st = (struct state *) closure;
422   if (screenhack_event_helper (dpy, window, event))
423     {
424       int i;
425       st->window_height--;
426       munch_reshape(dpy, window, closure, st->window_width, st->window_height);
427       st->mismunch = random() & 1;
428       for (i = 0; i < st->simul; i++) {
429         free (st->munchers[i]);
430         st->munchers[i] = make_muncher(st);
431       }
432       XClearWindow(dpy, window);
433       return True;
434     }
435   return False;
436 }
437
438 static void
439 munch_free (Display *dpy, Window window, void *closure)
440 {
441   struct state *st = (struct state *) closure;
442   free (st);
443 }
444
445
446 static const char *munch_defaults [] = {
447   ".lowrez:           true",
448   ".background:       black",
449   ".foreground:       white",
450   "*fpsSolid:         true",
451   "*delay:            10000",
452   "*mismunch:         random",
453   "*simul:            5",
454   "*clear:            65",
455   "*xor:              True",
456 #ifdef HAVE_MOBILE
457   "*ignoreRotation:   True",
458 #endif
459
460   0
461 };
462
463 static XrmOptionDescRec munch_options [] = {
464   { "-delay",         ".delay",       XrmoptionSepArg,  0 },
465   { "-simul",         ".simul",       XrmoptionSepArg,  0 },
466   { "-clear",         ".clear",       XrmoptionSepArg, "true" },
467   { "-xor",           ".xor",         XrmoptionNoArg,  "true" },
468   { "-no-xor",        ".xor",         XrmoptionNoArg,  "false" },
469   { "-classic",       ".mismunch",    XrmoptionNoArg,  "false" },
470   { "-mismunch",      ".mismunch",    XrmoptionNoArg,  "true" },
471   { "-random",        ".mismunch",    XrmoptionNoArg,  "random" },
472   { 0, 0, 0, 0 }
473 };
474
475
476 XSCREENSAVER_MODULE ("Munch", munch)