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 */
110 FLOAT instantdeathchan;
124 #define cell_x(c) (st->arr_width ? ((c) - st->arr) % st->arr_width : 0)
125 #define cell_y(c) (st->arr_width ? ((c) - st->arr) / st->arr_width : 0)
128 static int random_life_value (struct state *st)
130 return (int) ((RAND_FLOAT * (st->maxlifespan - st->minlifespan)) + st->minlifespan);
133 static void setup_random_colormap (struct state *st, XWindowAttributes *xgwa)
137 int ncolors = st->count - 1;
139 XColor *colors = (XColor *) calloc (sizeof(*colors), st->count*2);
141 colors[0].pixel = get_pixel_resource (st->dpy, xgwa->colormap,
142 "background", "Background");
144 make_random_colormap (xgwa->screen, xgwa->visual, xgwa->colormap,
145 colors+1, &ncolors, True, True, 0, True);
148 fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
155 memcpy (colors + st->count, colors, st->count * sizeof(*colors));
156 colors[st->count].pixel = get_pixel_resource (st->dpy, xgwa->colormap,
157 "foreground", "Foreground");
159 for (n = 1; n < st->count; n++)
161 int m = n + st->count;
162 colors[n].red = colors[m].red / 2;
163 colors[n].green = colors[m].green / 2;
164 colors[n].blue = colors[m].blue / 2;
166 if (!XAllocColor (st->dpy, xgwa->colormap, &colors[n]))
169 colors[n] = colors[m];
176 "%s: unable to allocate %d half-intensity colors.\n",
180 for (n = 0; n < st->count*2; n++)
182 gcv.foreground = colors[n].pixel;
183 st->coloredGCs[n] = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
189 static void setup_original_colormap (struct state *st, XWindowAttributes *xgwa)
194 XColor *colors = (XColor *) calloc (sizeof(*colors), st->count*2);
196 colors[0].pixel = get_pixel_resource (st->dpy, xgwa->colormap,
197 "background", "Background");
199 colors[st->count].pixel = get_pixel_resource (st->dpy, xgwa->colormap,
200 "foreground", "Foreground");
202 for (n = 1; n < st->count; n++)
204 int m = n + st->count;
205 colors[n].red = ((n & 0x01) != 0) * 0x8000;
206 colors[n].green = ((n & 0x02) != 0) * 0x8000;
207 colors[n].blue = ((n & 0x04) != 0) * 0x8000;
209 if (!XAllocColor (st->dpy, xgwa->colormap, &colors[n]))
212 colors[n] = colors[0];
215 colors[m].red = colors[n].red + 0x4000;
216 colors[m].green = colors[n].green + 0x4000;
217 colors[m].blue = colors[n].blue + 0x4000;
219 if (!XAllocColor (st->dpy, xgwa->colormap, &colors[m]))
222 colors[m] = colors[st->count];
229 "%s: unable to allocate %d colors.\n",
233 for (n = 0; n < st->count*2; n++)
235 gcv.foreground = colors[n].pixel;
236 st->coloredGCs[n] = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
243 setup_display (struct state *st)
245 XWindowAttributes xgwa;
247 int cell_size = get_integer_resource (st->dpy, "size", "Integer");
248 int osize, alloc_size, oalloc;
249 int mem_throttle = 0;
252 if (cell_size < 1) cell_size = 1;
256 s = get_string_resource (st->dpy, "memThrottle", "MemThrottle");
261 if (1 == sscanf (s, " %d M %c", &n, &c) ||
262 1 == sscanf (s, " %d m %c", &n, &c))
263 mem_throttle = n * (1 << 20);
264 else if (1 == sscanf (s, " %d K %c", &n, &c) ||
265 1 == sscanf (s, " %d k %c", &n, &c))
266 mem_throttle = n * (1 << 10);
267 else if (1 == sscanf (s, " %d %c", &n, &c))
271 fprintf (stderr, "%s: invalid memThrottle \"%s\" (try \"10M\")\n",
279 XGetWindowAttributes (st->dpy, st->window, &xgwa);
281 st->originalcolors = get_boolean_resource (st->dpy, "originalcolors", "Boolean");
283 st->count = get_integer_resource (st->dpy, "count", "Integer");
284 if (st->count < 2) st->count = 2;
286 /* number of colors can't be greater than the half depth of the screen. */
287 if (st->count > (unsigned int) (1L << (xgwa.depth-1)))
288 st->count = (unsigned int) (1L << (xgwa.depth-1));
290 /* Actually, since cell->col is of type char, this has to be small. */
291 if (st->count >= (unsigned int) (1L << ((sizeof(st->arr[0].col) * 8) - 1)))
292 st->count = (unsigned int) (1L << ((sizeof(st->arr[0].col) * 8) - 1));
295 if (st->originalcolors && (st->count > 8))
300 st->coloredGCs = (GC *) calloc (sizeof(GC), st->count * 2);
302 st->diaglim = get_float_resource (st->dpy, "diaglim", "Float");
303 if (st->diaglim < 1.0)
307 else if (st->diaglim > 2.0)
311 st->diaglim *= st->orthlim;
313 st->anychan = get_float_resource (st->dpy, "anychan", "Float");
314 if (st->anychan < 0.0)
318 else if (st->anychan > 1.0)
323 st->minorchan = get_float_resource (st->dpy, "minorchan","Float");
324 if (st->minorchan < 0.0)
328 else if (st->minorchan > 1.0)
333 st->instantdeathchan = get_float_resource (st->dpy, "instantdeathchan","Float");
334 if (st->instantdeathchan < 0.0)
336 st->instantdeathchan = 0.0;
338 else if (st->instantdeathchan > 1.0)
340 st->instantdeathchan = 1.0;
343 st->minlifespan = get_integer_resource (st->dpy, "minlifespan", "Integer");
344 if (st->minlifespan < 1)
349 st->maxlifespan = get_integer_resource (st->dpy, "maxlifespan", "Integer");
350 if (st->maxlifespan < st->minlifespan)
352 st->maxlifespan = st->minlifespan;
355 st->minlifespeed = get_float_resource (st->dpy, "minlifespeed", "Float");
356 if (st->minlifespeed < 0.0)
358 st->minlifespeed = 0.0;
360 else if (st->minlifespeed > 1.0)
362 st->minlifespeed = 1.0;
365 st->maxlifespeed = get_float_resource (st->dpy, "maxlifespeed", "Float");
366 if (st->maxlifespeed < st->minlifespeed)
368 st->maxlifespeed = st->minlifespeed;
370 else if (st->maxlifespeed > 1.0)
372 st->maxlifespeed = 1.0;
375 st->mindeathspeed = get_float_resource (st->dpy, "mindeathspeed", "Float");
376 if (st->mindeathspeed < 0.0)
378 st->mindeathspeed = 0.0;
380 else if (st->mindeathspeed > 1.0)
382 st->mindeathspeed = 1.0;
385 st->maxdeathspeed = get_float_resource (st->dpy, "maxdeathspeed", "Float");
386 if (st->maxdeathspeed < st->mindeathspeed)
388 st->maxdeathspeed = st->mindeathspeed;
390 else if (st->maxdeathspeed > 1.0)
392 st->maxdeathspeed = 1.0;
395 st->minlifespeed *= st->diaglim;
396 st->maxlifespeed *= st->diaglim;
397 st->mindeathspeed *= st->diaglim;
398 st->maxdeathspeed *= st->diaglim;
400 st->windowWidth = xgwa.width;
401 st->windowHeight = xgwa.height;
403 st->arr_width = st->windowWidth / cell_size;
404 st->arr_height = st->windowHeight / cell_size;
406 alloc_size = sizeof(cell) * st->arr_width * st->arr_height;
409 if (mem_throttle > 0)
410 while (cell_size < st->windowWidth/10 &&
411 cell_size < st->windowHeight/10 &&
412 alloc_size > mem_throttle)
415 st->arr_width = st->windowWidth / cell_size;
416 st->arr_height = st->windowHeight / cell_size;
417 alloc_size = sizeof(cell) * st->arr_width * st->arr_height;
420 if (osize != cell_size)
425 "%s: throttling cell size from %d to %d because of %dM limit.\n",
426 progname, osize, cell_size, mem_throttle / (1 << 20));
427 fprintf (stderr, "%s: %dx%dx%d = %.1fM, %dx%dx%d = %.1fM.\n",
429 st->windowWidth, st->windowHeight, osize,
430 ((float) oalloc) / (1 << 20),
431 st->windowWidth, st->windowHeight, cell_size,
432 ((float) alloc_size) / (1 << 20));
437 st->xSize = st->arr_width ? st->windowWidth / st->arr_width : 0;
438 st->ySize = st->arr_height ? st->windowHeight / st->arr_height : 0;
439 if (st->xSize > st->ySize)
441 st->xSize = st->ySize;
445 st->ySize = st->xSize;
448 st->xOffset = (st->windowWidth - (st->arr_width * st->xSize)) / 2;
449 st->yOffset = (st->windowHeight - (st->arr_height * st->ySize)) / 2;
451 if (st->originalcolors)
453 setup_original_colormap (st, &xgwa);
457 setup_random_colormap (st, &xgwa);
461 static void drawblock (struct state *st, int x, int y, unsigned char c)
463 if (st->xSize == 1 && st->ySize == 1)
464 XDrawPoint (st->dpy, st->window, st->coloredGCs[c], x + st->xOffset, y + st->yOffset);
466 XFillRectangle (st->dpy, st->window, st->coloredGCs[c],
467 x * st->xSize + st->xOffset, y * st->ySize + st->yOffset,
468 st->xSize, st->ySize);
471 static void setup_arr (struct state *st)
480 XFillRectangle (st->dpy, st->window, st->coloredGCs[0], 0, 0,
481 st->windowWidth, st->windowHeight);
483 if (!st->arr_width) st->arr_width = 1;
484 if (!st->arr_height) st->arr_height = 1;
486 st->arr = (cell *) calloc (sizeof(cell), st->arr_width * st->arr_height);
489 fprintf (stderr, "%s: out of memory allocating %dx%d grid\n",
490 progname, st->arr_width, st->arr_height);
494 for (y = 0; y < st->arr_height; y++)
496 int row = y * st->arr_width;
497 for (x = 0; x < st->arr_width; x++)
499 st->arr[row+x].speed = 0.0;
500 st->arr[row+x].growth = 0.0;
501 st->arr[row+x].col = 0;
502 st->arr[row+x].isnext = 0;
503 st->arr[row+x].next = 0;
504 st->arr[row+x].prev = 0;
508 if (st->head == NULL)
510 st->head = (cell *) malloc (sizeof (cell));
513 if (st->tail == NULL)
515 st->tail = (cell *) malloc (sizeof (cell));
518 st->head->next = st->tail;
519 st->head->prev = st->head;
520 st->tail->next = st->tail;
521 st->tail->prev = st->head;
523 st->blastcount = random_life_value (st);
526 static void newcell (struct state *st, cell *c, unsigned char col, FLOAT sp)
530 if (c->col == col) return;
537 c->next = st->head->next;
544 static void killcell (struct state *st, cell *c)
546 c->prev->next = c->next;
547 c->next->prev = c->prev;
550 drawblock (st, cell_x(c), cell_y(c), c->col);
554 static void randblip (struct state *st, int doit)
559 && (st->blastcount-- >= 0)
560 && (RAND_FLOAT > st->anychan))
565 if (st->blastcount < 0)
569 st->blastcount = random_life_value (st);
570 if (RAND_FLOAT < st->instantdeathchan)
572 /* clear everything every so often to keep from getting into a
578 else if (RAND_FLOAT <= st->minorchan)
584 n = random () % 3 + 3;
589 int x = st->arr_width ? random () % st->arr_width : 0;
590 int y = st->arr_height ? random () % st->arr_height : 0;
596 s = RAND_FLOAT * (st->maxdeathspeed - st->mindeathspeed) + st->mindeathspeed;
600 c = ((st->count - 1) ? random () % (st->count-1) : 0) + 1;
601 s = RAND_FLOAT * (st->maxlifespeed - st->minlifespeed) + st->minlifespeed;
603 newcell (st, &st->arr[y * st->arr_width + x], c, s);
607 static void update (struct state *st)
611 for (a = st->head->next; a != st->tail; a = a->next)
613 static const XPoint all_coords[] = {{-1, -1}, {-1, 1}, {1, -1}, {1, 1},
614 {-1, 0}, { 1, 0}, {0, -1}, {0, 1},
617 const XPoint *coords = 0;
619 if (a->speed == 0) continue;
620 a->growth += a->speed;
622 if (a->growth >= st->diaglim)
626 else if (a->growth >= st->orthlim)
628 coords = &all_coords[4];
635 while (coords->x != 99)
637 int x = cell_x(a) + coords->x;
638 int y = cell_y(a) + coords->y;
641 if (x < 0) x = st->arr_width - 1;
642 else if (x >= st->arr_width) x = 0;
644 if (y < 0) y = st->arr_height - 1;
645 else if (y >= st->arr_height) y = 0;
647 newcell (st, &st->arr[y * st->arr_width + x], a->col, a->speed);
650 if (a->growth >= st->diaglim)
654 randblip (st, (st->head->next) == st->tail);
656 for (a = st->head->next; a != st->tail; a = a->next)
661 a->speed = a->nextspeed;
664 drawblock (st, cell_x(a), cell_y(a), a->col + st->count);
670 petri_init (Display *dpy, Window win)
672 struct state *st = (struct state *) calloc (1, sizeof(*st));
676 st->delay = get_integer_resource (st->dpy, "delay", "Delay");
687 petri_draw (Display *dpy, Window window, void *closure)
689 struct state *st = (struct state *) closure;
695 petri_reshape (Display *dpy, Window window, void *closure,
696 unsigned int w, unsigned int h)
701 petri_event (Display *dpy, Window window, void *closure, XEvent *event)
707 petri_free (Display *dpy, Window window, void *closure)
709 struct state *st = (struct state *) closure;
715 static const char *petri_defaults [] = {
716 ".background: black",
717 ".foreground: white",
725 "*instantdeathchan: 0.2",
727 "*maxlifespan: 1500",
728 "*minlifespeed: 0.04",
729 "*maxlifespeed: 0.13",
730 "*mindeathspeed: 0.42",
731 "*maxdeathspeed: 0.46",
732 "*originalcolors: false",
733 "*memThrottle: 22M", /* don't malloc more than this much.
734 Scale the pixels up if necessary. */
736 "*ignoreRotation: True",
741 static XrmOptionDescRec petri_options [] = {
742 { "-delay", ".delay", XrmoptionSepArg, 0 },
743 { "-size", ".size", XrmoptionSepArg, 0 },
744 { "-count", ".count", XrmoptionSepArg, 0 },
745 { "-diaglim", ".diaglim", XrmoptionSepArg, 0 },
746 { "-anychan", ".anychan", XrmoptionSepArg, 0 },
747 { "-minorchan", ".minorchan", XrmoptionSepArg, 0 },
748 { "-instantdeathchan", ".instantdeathchan", XrmoptionSepArg, 0 },
749 { "-minlifespan", ".minlifespan", XrmoptionSepArg, 0 },
750 { "-maxlifespan", ".maxlifespan", XrmoptionSepArg, 0 },
751 { "-minlifespeed", ".minlifespeed", XrmoptionSepArg, 0 },
752 { "-maxlifespeed", ".maxlifespeed", XrmoptionSepArg, 0 },
753 { "-mindeathspeed", ".mindeathspeed", XrmoptionSepArg, 0 },
754 { "-maxdeathspeed", ".maxdeathspeed", XrmoptionSepArg, 0 },
755 { "-originalcolors", ".originalcolors", XrmoptionNoArg, "true" },
756 { "-mem-throttle", ".memThrottle", XrmoptionSepArg, 0 },
761 XSCREENSAVER_MODULE ("Petri", petri)