X-Git-Url: https://git.subgeniuskitty.com/screensavers/.git/blobdiff_plain/76b9ae922f4d8aac0033b88da240b5c75f16cdd3..d918dd367d8e5524f18869b132d355857fb53794:/hacks/WolframAutomata/WolframAutomata.c diff --git a/hacks/WolframAutomata/WolframAutomata.c b/hacks/WolframAutomata/WolframAutomata.c index 59562b3..35482df 100644 --- a/hacks/WolframAutomata/WolframAutomata.c +++ b/hacks/WolframAutomata/WolframAutomata.c @@ -49,6 +49,7 @@ // -background "COLORNAME" // (default is black and white) // (mention sample color combinations in manpage, and link to: https://en.wikipedia.org/wiki/X11_color_names) +// (note to the user that most color names they can naturally think of (e.g. red, purple, gray, pink, etc) are valid X11 color names for these CLI options.) // display info overlay with CA number and start conditions? // -overlay // which ruleset number to use? Or random? Or random from small set of hand-selected interesting examples? @@ -166,9 +167,35 @@ struct color_pair { }; // TODO: Decorations -// TODO: Populate this table with more examples. static const struct color_pair color_list[] = { + {"red", "black"}, + {"olive", "black"}, + {"teal", "black"}, + {"slateblue", "black"}, + {"violet", "black"}, + {"purple", "black"}, {"white", "black"}, + {"white", "darkgreen"}, + {"white", "darkmagenta"}, + {"white", "darkred"}, + {"white", "darkblue"}, + {"darkslategray", "darkslategray1"}, + {"lightsteelblue", "darkslategray"}, + {"royalblue4", "royalblue"}, + {"antiquewhite2", "antiquewhite4"}, + {"darkolivegreen1", "darkolivegreen"}, + {"darkseagreen1", "darkseagreen4"}, + {"pink", "darkred"}, + {"lightblue", "darkgreen"}, + {"red", "blue"}, + {"red", "darkgreen"}, + {"aqua", "teal"}, + {"darkblue", "teal"}, + {"khaki", "seagreen"}, + {"khaki", "darkolivegreen"}, + {"lightslategray", "darkslategray"}, + {"tomato", "darkslategray"}, + {"tomato", "darkcyan"} }; /* -------------------------------------------------------------------------- */ @@ -227,6 +254,10 @@ render_current_generation(struct state * state) for (xpos = 0; xpos < state->number_of_cells; xpos++) { if (state->current_generation[xpos] == True) { XFillRectangle(state->dpy, state->evolution_history, state->gc, xpos*state->pixel_size, state->ypos, state->pixel_size, state->pixel_size); + } else { + XSetForeground(state->dpy, state->gc, state->bg); + XFillRectangle(state->dpy, state->evolution_history, state->gc, xpos*state->pixel_size, state->ypos, state->pixel_size, state->pixel_size); + XSetForeground(state->dpy, state->gc, state->fg); } } } @@ -267,22 +298,83 @@ WolframAutomata_init(Display * dpy, Window win) state->bg = gcv.background = get_pixel_resource(state->dpy, xgwa.colormap, "background", "Background"); state->gc = XCreateGC(state->dpy, state->win, GCForeground, &gcv); - state->delay_microsec = get_integer_resource(state->dpy, "delay-usec", "Integer"); - if (state->delay_microsec < 0) state->delay_microsec = 0; - - state->pixel_size = get_integer_resource(state->dpy, "pixel-size", "Integer"); + /* Set the size of each simulated cell as NxN pixels for pixel_size=N. */ + if (get_boolean_resource(state->dpy, "random-pixel-size", "Boolean")) { + /* Although we are choosing the pixel size 'randomly', a truly random */ + /* selection would bias toward large numbers since there are more of */ + /* them. To avoid this, we select a random number for a bit shift, */ + /* resulting in a pixel size of 1, 2, 4, 8, 16 or 32, equally likely. */ + state->pixel_size = 1 << (random() % 6); + } else { + state->pixel_size = get_integer_resource(state->dpy, "pixel-size", "Integer"); + } if (state->pixel_size < 1) state->pixel_size = 1; if (state->pixel_size > state->xlim) state->pixel_size = state->xlim; state->number_of_cells = state->xlim / state->pixel_size; // TODO: Do we want to enforce that number_of_cells > 0? + /* Set the delay (in microseconds) between simulation of each generation */ + /* of the simulation, also known as the delay between calls to */ + /* WolframAutomata_draw(), which simulates one generation per call. */ + if (get_boolean_resource(state->dpy, "random-delay", "Boolean")) { + /* When randomly setting the delay, the problem is to avoid being too */ + /* fast or too slow, as well as ensuring slower speeds are chosen */ + /* with the same likelihood as faster speeds, as perceived by a */ + /* human. By empirical observation, we note that for 1x1 up to 4x4 */ + /* pixel cell sizes, values for state->delay_microsec between */ + /* 2048 (2^11) and 16556 (2^14) produce pleasant scroll rates. To */ + /* maintain this appearance, we bitshift state->pixel_size down until */ + /* it is a maximum of 4x4 pixels in size, record how many bitshifts */ + /* took place, and then shift our valid window for */ + /* state->delay_microsec up by an equal number of bitshifts. For */ + /* example, if state->pixel_size=9, then it takes one right shift to */ + /* reach state->pixel_size=4. Thus, the valid window for */ + /* state->delay_microsec becomes 4096 (2^12) up to 32768 (2^15). */ + size_t pixel_shift_range = 1; + size_t pixel_size_temp = state->pixel_size; + while (pixel_size_temp > 4) { + pixel_size_temp >>= 1; + pixel_shift_range++; + } + /* In the below line, '3' represents the total range, namely '14-11' */ + /* from '2^14' and '2^11' as the endpoints. Similarly, the '11' in */ + /* the below line represents the starting point of this range, from */ + /* the exponent in '2^11'. */ + state->delay_microsec = 1 << ((random() % 3) + 11 + pixel_shift_range); + } else { + state->delay_microsec = get_integer_resource(state->dpy, "delay-usec", "Integer"); + } + if (state->delay_microsec < 0) state->delay_microsec = 0; + + /* Set the number of generations to simulate before wiping the simulation */ + /* and re-running with new settings. */ + if (get_boolean_resource(state->dpy, "random-num-generations", "Boolean")) { + /* By empirical observation, keep the product */ + /* state->num_generations * state->pixel_size */ + /* below 10,000 to avoid BadAlloc errors from the X server due to */ + /* requesting an enormous pixmap. This value works on both a 12 core */ + /* Xeon with 108 GiB of RAM and a Sun Ultra 2 with 2 GiB of RAM. */ + state->num_generations = random() % (10000 / state->pixel_size); + /* Ensure selected value is large enough to at least fill the screen. */ + /* Cast to avoid overflow. */ + if ((long)state->num_generations * (long)state->pixel_size < state->ylim) { + state->num_generations = (state->ylim / state->pixel_size) + 1; + } + } else { + state->num_generations = get_integer_resource(state->dpy, "num-generations", "Integer"); + } /* The minimum number of generations is 2 since we must allocate enough */ /* space to hold the seed generation and at least one pass through */ /* WolframAutomata_draw(), which is where we check whether or not we've */ /* reached the end of the pixmap. */ - state->num_generations = get_integer_resource(state->dpy, "num-generations", "Integer"); if (state->num_generations < 0) state->num_generations = 2; + /* The maximum number of generations is pixel_size dependent. This is a */ + /* soft limit and may be increased if you have plenty of RAM (and a */ + /* cooperative X server). The value 10,000 was determined empirically. */ + if ((long)state->num_generations * (long)state->pixel_size > 10000) { + state->num_generations = 10000 / state->pixel_size; + } /* Time to figure out which rule to use for this simulation. */ /* We ignore any weirdness resulting from the following cast since every */ @@ -343,7 +435,9 @@ WolframAutomata_init(Display * dpy, Window win) state->evolution_history = XCreatePixmap(state->dpy, state->win, state->xlim, state->num_generations*state->pixel_size, xgwa.depth); // Pixmap contents are undefined after creation. Explicitly set a black // background by drawing a black rectangle over the entire pixmap. - XSetForeground(state->dpy, state->gc, state->bg); + XColor blackx, blacks; + XAllocNamedColor(state->dpy, DefaultColormapOfScreen(DefaultScreenOfDisplay(state->dpy)), "black", &blacks, &blackx); + XSetForeground(state->dpy, state->gc, blacks.pixel); XFillRectangle(state->dpy, state->evolution_history, state->gc, 0, 0, state->xlim, state->num_generations*state->pixel_size); XSetForeground(state->dpy, state->gc, state->fg); render_current_generation(state); @@ -417,6 +511,9 @@ static const char * WolframAutomata_defaults[] = { "*rule-random: False", "*population-density: 50", "*population-single: False", + "*random-delay: False", + "*random-pixel-size: False", + "*random-num-generations: False", 0 }; @@ -432,6 +529,10 @@ static XrmOptionDescRec WolframAutomata_options[] = { { "-rule-random", ".rule-random", XrmoptionNoArg, "True" }, { "-population-density", ".population-density", XrmoptionSepArg, 0 }, { "-population-single", ".population-single", XrmoptionNoArg, "True" }, + { "-random-delay", ".random-delay", XrmoptionNoArg, "True" }, + { "-random-pixel-size", ".random-pixel-size", XrmoptionNoArg, "True" }, + { "-random-num-generations", ".random-num-generations", XrmoptionNoArg, "True" }, + { 0, 0, 0, 0 } };