1 /* petri, simulate mold in a petri dish. v2.7
2 * by Dan Bornstein, danfuzz@milk.com
3 * with help from Jamie Zawinski, jwz@jwz.org
4 * Copyright (c) 1992-1999 Dan Bornstein.
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation. No representations are made about the suitability of this
11 * software for any purpose. It is provided "as is" without express or
15 * Brief description of options/resources:
17 * delay: the delay in microseconds between iterations
18 * size: the size of a cell in pixels
19 * count: the number of different kinds of mold (minimum: 2)
20 * diaglim: the age limit for diagonal growth as a multiplier of orthogonal
21 * growth (minimum: 1, maximum 2). 1 means square growth, 1.414
22 * (i.e., sqrt(2)) means approximately circular growth, 2 means diamond
24 * anychan: the chance (fraction, between 0 and 1) that at each iteration,
25 * any new cell will be born
26 * minorchan: the chance (fraction, between 0 and 1) that, given that new
27 * cells will be added, that only two will be added (a minor cell birth
29 * instantdeathchan: the chance (fraction, between 0 and 1) that, given
30 * that death and destruction will happen, that instead of using plague
31 * cells, death will be instantaneous
32 * minlifespan: the minimum lifespan of a colony (before black death ensues)
33 * maxlifespan: the maximum lifespan of a colony (before black death ensues)
34 * minlifespeed: the minimum speed for living cells as a fraction of the
35 * maximum possible speed (fraction, between 0 and 1)
36 * maxlifespeed: the maximum speed for living cells as a fraction of the
37 * maximum possible speed (fraction, between 0 and 1)
38 * mindeathspeed: the minimum speed for black death cells as a fraction of the
39 * maximum possible speed (fraction, between 0 and 1)
40 * maxdeathspeed: the maximum speed for black death cells as a fraction of the
41 * maximum possible speed (fraction, between 0 and 1)
42 * originalcolors: if true, count must be 8 or less and the colors are a
43 * fixed set of primary and secondary colors (the artist's original choices)
45 * Interesting settings:
47 * petri -originalcolors -size 8
49 * petri -size 8 -diaglim 1.8
52 * petri -count 4 -anychan 0.01 -minorchan 1 \
53 * -minlifespan 2000 -maxlifespan 5000
55 * petri -count 3 -anychan 1 -minlifespan 100000 \
58 * petri -minlifespeed 0.02 -maxlifespeed 0.03 -minlifespan 1 \
59 * -maxlifespan 1 -instantdeathchan 0 -minorchan 0 \
60 * -anychan 0.3 -delay 4000
64 #include "screenhack.h"
68 #define RAND_FLOAT (((FLOAT) (random() & 0xffff)) / ((FLOAT) 0x10000))
72 unsigned char col; /* 0 */
73 unsigned char isnext; /* 1 */
74 unsigned char nextcol; /* 2 */
76 struct cell_s *next; /* 4 */
77 struct cell_s *prev; /* 8 - */
79 FLOAT growth; /* 16 20 - */
80 FLOAT nextspeed; /* 20 28 */
85 static int arr_height;
91 static int blastcount;
93 static Display *display;
95 static GC *coloredGCs;
97 static int windowWidth;
98 static int windowHeight;
104 static FLOAT orthlim = 1.0;
105 static FLOAT diaglim;
106 static FLOAT anychan;
107 static FLOAT minorchan;
108 static FLOAT instantdeathchan;
109 static int minlifespan;
110 static int maxlifespan;
111 static FLOAT minlifespeed;
112 static FLOAT maxlifespeed;
113 static FLOAT mindeathspeed;
114 static FLOAT maxdeathspeed;
115 static Bool originalcolors;
117 #define cell_x(c) (((c) - arr) % arr_width)
118 #define cell_y(c) (((c) - arr) / arr_width)
121 static int random_life_value (void)
123 return (int) ((RAND_FLOAT * (maxlifespan - minlifespan)) + minlifespan);
126 static void setup_random_colormap (XWindowAttributes *xgwa)
130 int ncolors = count - 1;
132 XColor *colors = (XColor *) calloc (sizeof(*colors), count*2);
134 colors[0].pixel = get_pixel_resource ("background", "Background",
135 display, xgwa->colormap);
137 make_random_colormap (display, xgwa->visual, xgwa->colormap,
138 colors+1, &ncolors, True, True, 0, True);
141 fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
148 memcpy (colors + count, colors, count * sizeof(*colors));
149 colors[count].pixel = get_pixel_resource ("foreground", "Foreground",
150 display, xgwa->colormap);
152 for (n = 1; n < count; n++)
155 colors[n].red = colors[m].red / 2;
156 colors[n].green = colors[m].green / 2;
157 colors[n].blue = colors[m].blue / 2;
159 if (!XAllocColor (display, xgwa->colormap, &colors[n]))
162 colors[n] = colors[m];
169 "%s: unable to allocate %d half-intensity colors.\n",
173 for (n = 0; n < count*2; n++)
175 gcv.foreground = colors[n].pixel;
176 coloredGCs[n] = XCreateGC (display, window, GCForeground, &gcv);
182 static void setup_original_colormap (XWindowAttributes *xgwa)
187 XColor *colors = (XColor *) calloc (sizeof(*colors), count*2);
189 colors[0].pixel = get_pixel_resource ("background", "Background",
190 display, xgwa->colormap);
192 colors[count].pixel = get_pixel_resource ("foreground", "Foreground",
193 display, xgwa->colormap);
195 for (n = 1; n < count; n++)
198 colors[n].red = ((n & 0x01) != 0) * 0x8000;
199 colors[n].green = ((n & 0x02) != 0) * 0x8000;
200 colors[n].blue = ((n & 0x04) != 0) * 0x8000;
202 if (!XAllocColor (display, xgwa->colormap, &colors[n]))
205 colors[n] = colors[0];
208 colors[m].red = colors[n].red + 0x4000;
209 colors[m].green = colors[n].green + 0x4000;
210 colors[m].blue = colors[n].blue + 0x4000;
212 if (!XAllocColor (display, xgwa->colormap, &colors[m]))
215 colors[m] = colors[count];
222 "%s: unable to allocate %d colors.\n",
226 for (n = 0; n < count*2; n++)
228 gcv.foreground = colors[n].pixel;
229 coloredGCs[n] = XCreateGC (display, window, GCForeground, &gcv);
235 static void setup_display (void)
237 XWindowAttributes xgwa;
240 int cell_size = get_integer_resource ("size", "Integer");
241 int osize, alloc_size, oalloc;
242 int mem_throttle = 0;
245 if (cell_size < 1) cell_size = 1;
249 s = get_string_resource ("memThrottle", "MemThrottle");
254 if (1 == sscanf (s, " %d M %c", &n, &c) ||
255 1 == sscanf (s, " %d m %c", &n, &c))
256 mem_throttle = n * (1 << 20);
257 else if (1 == sscanf (s, " %d K %c", &n, &c) ||
258 1 == sscanf (s, " %d k %c", &n, &c))
259 mem_throttle = n * (1 << 10);
260 else if (1 == sscanf (s, " %d %c", &n, &c))
264 fprintf (stderr, "%s: invalid memThrottle \"%s\" (try \"10M\")\n",
272 XGetWindowAttributes (display, window, &xgwa);
274 originalcolors = get_boolean_resource ("originalcolors", "Boolean");
276 count = get_integer_resource ("count", "Integer");
277 if (count < 2) count = 2;
279 /* number of colors can't be greater than the half depth of the screen. */
280 if (count > (1L << (xgwa.depth-1)))
281 count = (1L << (xgwa.depth-1));
283 /* Actually, since cell->col is of type char, this has to be small. */
284 if (count >= (1L << ((sizeof(arr[0].col) * 8) - 1)))
285 count = (1L << ((sizeof(arr[0].col) * 8) - 1));
288 if (originalcolors && (count > 8))
293 coloredGCs = (GC *) calloc (sizeof(GC), count * 2);
295 diaglim = get_float_resource ("diaglim", "Float");
300 else if (diaglim > 2.0)
306 anychan = get_float_resource ("anychan", "Float");
311 else if (anychan > 1.0)
316 minorchan = get_float_resource ("minorchan","Float");
321 else if (minorchan > 1.0)
326 instantdeathchan = get_float_resource ("instantdeathchan","Float");
327 if (instantdeathchan < 0.0)
329 instantdeathchan = 0.0;
331 else if (instantdeathchan > 1.0)
333 instantdeathchan = 1.0;
336 minlifespan = get_integer_resource ("minlifespan", "Integer");
342 maxlifespan = get_integer_resource ("maxlifespan", "Integer");
343 if (maxlifespan < minlifespan)
345 maxlifespan = minlifespan;
348 minlifespeed = get_float_resource ("minlifespeed", "Float");
349 if (minlifespeed < 0.0)
353 else if (minlifespeed > 1.0)
358 maxlifespeed = get_float_resource ("maxlifespeed", "Float");
359 if (maxlifespeed < minlifespeed)
361 maxlifespeed = minlifespeed;
363 else if (maxlifespeed > 1.0)
368 mindeathspeed = get_float_resource ("mindeathspeed", "Float");
369 if (mindeathspeed < 0.0)
373 else if (mindeathspeed > 1.0)
378 maxdeathspeed = get_float_resource ("maxdeathspeed", "Float");
379 if (maxdeathspeed < mindeathspeed)
381 maxdeathspeed = mindeathspeed;
383 else if (maxdeathspeed > 1.0)
388 minlifespeed *= diaglim;
389 maxlifespeed *= diaglim;
390 mindeathspeed *= diaglim;
391 maxdeathspeed *= diaglim;
393 cmap = xgwa.colormap;
395 windowWidth = xgwa.width;
396 windowHeight = xgwa.height;
398 arr_width = windowWidth / cell_size;
399 arr_height = windowHeight / cell_size;
401 alloc_size = sizeof(cell) * arr_width * arr_height;
404 if (mem_throttle > 0)
405 while (cell_size < windowWidth/10 &&
406 cell_size < windowHeight/10 &&
407 alloc_size > mem_throttle)
410 arr_width = windowWidth / cell_size;
411 arr_height = windowHeight / cell_size;
412 alloc_size = sizeof(cell) * arr_width * arr_height;
415 if (osize != cell_size)
417 static int warned = 0;
421 "%s: throttling cell size from %d to %d because of %dM limit.\n",
422 progname, osize, cell_size, mem_throttle / (1 << 20));
423 fprintf (stderr, "%s: %dx%dx%d = %.1fM, %dx%dx%d = %.1fM.\n",
425 windowWidth, windowHeight, osize,
426 ((float) oalloc) / (1 << 20),
427 windowWidth, windowHeight, cell_size,
428 ((float) alloc_size) / (1 << 20));
433 xSize = windowWidth / arr_width;
434 ySize = windowHeight / arr_height;
444 xOffset = (windowWidth - (arr_width * xSize)) / 2;
445 yOffset = (windowHeight - (arr_height * ySize)) / 2;
449 setup_original_colormap (&xgwa);
453 setup_random_colormap (&xgwa);
457 static void drawblock (int x, int y, unsigned char c)
459 if (xSize == 1 && ySize == 1)
460 XDrawPoint (display, window, coloredGCs[c], x + xOffset, y + yOffset);
462 XFillRectangle (display, window, coloredGCs[c],
463 x * xSize + xOffset, y * ySize + yOffset,
467 static void setup_arr (void)
476 XFillRectangle (display, window, coloredGCs[0], 0, 0,
477 windowWidth, windowHeight);
479 arr = (cell *) calloc (sizeof(cell), arr_width * arr_height);
482 fprintf (stderr, "%s: out of memory allocating %dx%d grid\n",
483 progname, arr_width, arr_height);
487 for (y = 0; y < arr_height; y++)
489 int row = y * arr_width;
490 for (x = 0; x < arr_width; x++)
492 arr[row+x].speed = 0.0;
493 arr[row+x].growth = 0.0;
495 arr[row+x].isnext = 0;
503 head = (cell *) malloc (sizeof (cell));
508 tail = (cell *) malloc (sizeof (cell));
516 blastcount = random_life_value ();
519 static void newcell (cell *c, unsigned char col, FLOAT sp)
523 if (c->col == col) return;
530 c->next = head->next;
537 static void killcell (cell *c)
539 c->prev->next = c->next;
540 c->next->prev = c->prev;
543 drawblock (cell_x(c), cell_y(c), c->col);
547 static void randblip (int doit)
552 && (blastcount-- >= 0)
553 && (RAND_FLOAT > anychan))
562 blastcount = random_life_value ();
563 if (RAND_FLOAT < instantdeathchan)
565 /* clear everything every so often to keep from getting into a
571 else if (RAND_FLOAT <= minorchan)
577 n = random () % 3 + 3;
582 int x = random () % arr_width;
583 int y = random () % arr_height;
589 s = RAND_FLOAT * (maxdeathspeed - mindeathspeed) + mindeathspeed;
593 c = (random () % (count-1)) + 1;
594 s = RAND_FLOAT * (maxlifespeed - minlifespeed) + minlifespeed;
596 newcell (&arr[y * arr_width + x], c, s);
600 static void update (void)
604 for (a = head->next; a != tail; a = a->next)
606 static XPoint all_coords[] = {{-1, -1}, {-1, 1}, {1, -1}, {1, 1},
607 {-1, 0}, { 1, 0}, {0, -1}, {0, 1},
612 if (a->speed == 0) continue;
613 a->growth += a->speed;
615 if (a->growth >= diaglim)
619 else if (a->growth >= orthlim)
621 coords = &all_coords[4];
628 while (coords->x != 99)
630 int x = cell_x(a) + coords->x;
631 int y = cell_y(a) + coords->y;
634 if (x < 0) x = arr_width - 1;
635 else if (x >= arr_width) x = 0;
637 if (y < 0) y = arr_height - 1;
638 else if (y >= arr_height) y = 0;
640 newcell (&arr[y * arr_width + x], a->col, a->speed);
643 if (a->growth >= diaglim)
647 randblip ((head->next) == tail);
649 for (a = head->next; a != tail; a = a->next)
654 a->speed = a->nextspeed;
657 drawblock (cell_x(a), cell_y(a), a->col + count);
663 char *progclass = "Petri";
665 char *defaults [] = {
666 ".background: black",
667 ".foreground: white",
674 "*instantdeathchan: 0.2",
676 "*maxlifespan: 1500",
677 "*minlifespeed: 0.04",
678 "*maxlifespeed: 0.13",
679 "*mindeathspeed: 0.42",
680 "*maxdeathspeed: 0.46",
681 "*originalcolors: false",
682 "*memThrottle: 22M", /* don't malloc more than this much.
683 Scale the pixels up if necessary. */
687 XrmOptionDescRec options [] = {
688 { "-delay", ".delay", XrmoptionSepArg, 0 },
689 { "-size", ".size", XrmoptionSepArg, 0 },
690 { "-count", ".count", XrmoptionSepArg, 0 },
691 { "-diaglim", ".diaglim", XrmoptionSepArg, 0 },
692 { "-anychan", ".anychan", XrmoptionSepArg, 0 },
693 { "-minorchan", ".minorchan", XrmoptionSepArg, 0 },
694 { "-instantdeathchan", ".instantdeathchan", XrmoptionSepArg, 0 },
695 { "-minlifespan", ".minlifespan", XrmoptionSepArg, 0 },
696 { "-maxlifespan", ".maxlifespan", XrmoptionSepArg, 0 },
697 { "-minlifespeed", ".minlifespeed", XrmoptionSepArg, 0 },
698 { "-maxlifespeed", ".maxlifespeed", XrmoptionSepArg, 0 },
699 { "-mindeathspeed", ".mindeathspeed", XrmoptionSepArg, 0 },
700 { "-maxdeathspeed", ".maxdeathspeed", XrmoptionSepArg, 0 },
701 { "-originalcolors", ".originalcolors", XrmoptionNoArg, "true" },
702 { "-mem-throttle", ".memThrottle", XrmoptionSepArg, 0 },
706 void screenhack (Display *dpy, Window win)
708 int delay = get_integer_resource ("delay", "Delay");
721 screenhack_handle_events (dpy);