X-Git-Url: http://git.subgeniuskitty.com/screensavers/.git/blobdiff_plain/4ff197f3ec1a643fef506c06ec4d25e5151a3312..90afc4a7b35dca2117bfa9f3e36c82424b0d3856:/hacks/WolframAutomata/WolframAutomata.c diff --git a/hacks/WolframAutomata/WolframAutomata.c b/hacks/WolframAutomata/WolframAutomata.c index 65a7e5c..3c7c573 100644 --- a/hacks/WolframAutomata/WolframAutomata.c +++ b/hacks/WolframAutomata/WolframAutomata.c @@ -1,8 +1,10 @@ -/* (c) 2021 Aaron Taylor */ -/* See LICENSE.txt file for copyright and license details. */ +/* (c) 2021 Aaron Taylor */ +/* See LICENSE.txt file for copyright and license details. */ #include "screenhack.h" +/* Keep this source code C89 compliant per XScreensaver's instructions. */ + /* -------------------------------------------------------------------------- */ /* Data Structures */ /* -------------------------------------------------------------------------- */ @@ -36,11 +38,6 @@ struct state { /* the 'evolution_history' Pixmap and subsequently ignored. */ Bool * current_generation; - /* When randomizing the seed generation, we can specify a population */ - /* density, or we can restrict to a single living cell. */ - int population_density; - Bool population_single; - /* For more information on the encoding used for rule_number and on the */ /* method used to apply it: https://en.wikipedia.org/wiki/Wolfram_code */ uint8_t rule_number; @@ -51,8 +48,9 @@ struct state { size_t admiration_delay; /* ...in seconds. */ /* The following values correspond directly to independent CLI options. */ - Bool rule_random; - uint8_t rule_requested; /* Note: Repurposing Rule 0 as null value. */ + Bool random_rule; + int requested_rule; + int seed_density; int cell_size; /* If cell_size=N then draw NxN pixels per cell. */ int delay_microsec; /* ...between calls to WolframAutomata_draw(). */ int num_generations; /* Reset simulation after this many generations. */ @@ -72,6 +70,8 @@ struct curated_ruleset { enum seed_population seed; }; +/* The following array contains rule numbers and starting seeds which were */ +/* preselected as being visually interesting. */ static const struct curated_ruleset curated_ruleset_list[] = { { 18, middle_cell}, { 30, middle_cell}, @@ -133,6 +133,9 @@ struct color_pair { unsigned short bg_red, bg_green, bg_blue; }; +/* Since randomly selected colors would occasionally produce visually */ +/* indistinguishable foreground/background pairs, this array provides a */ +/* preselected list of complementary color pairs. */ static const struct color_pair color_list[] = { /* For mapping X11 color names to RGB values: */ /* https://www.ehdp.com/methods/x11-color-names-rgb-values.htm */ @@ -176,12 +179,25 @@ static const struct color_pair color_list[] = { /* Helper Functions */ /* -------------------------------------------------------------------------- */ +/* Some rules demonstrate behavior dominated by the starting seed. Thus, in */ +/* addition to a 50/50 random split of active/inactive cells, include other, */ +/* more biased random distributions in order to demonstrate such behavior. */ +static void +randomize_seed_density(struct state * state) +{ + switch (random() % 3) { + case 0: state->seed_density = 30; break; + case 1: state->seed_density = 50; break; + case 2: state->seed_density = 70; break; + } +} + static void generate_random_seed(struct state * state) { int i; for (i = 0; i < state->number_of_cells; i++) { - state->current_generation[i] = ((random() % 100) < state->population_density) ? True : False; + state->current_generation[i] = ((random() % 100) < state->seed_density) ? True : False; } } @@ -258,16 +274,20 @@ WolframAutomata_free(Display * dpy, Window win, void * closure) static void * WolframAutomata_init(Display * dpy, Window win) { - struct state * state = calloc(1, sizeof(*state)); + struct state * state; + XGCValues gcv; + XWindowAttributes xgwa; + XColor fg, bg; + XColor blackx, blacks; + size_t color_index; + const struct curated_ruleset * curated_ruleset = NULL; + + state = calloc(1, sizeof(*state)); if (!state) { fprintf(stderr, "ERROR: Failed to calloc() for state struct in WolframAutomata_init().\n"); exit(EXIT_FAILURE); } - XGCValues gcv; - XWindowAttributes xgwa; - const struct curated_ruleset * curated_ruleset = NULL; - state->dpy = dpy; state->win = win; @@ -282,23 +302,19 @@ WolframAutomata_init(Display * dpy, Window win) /* Set foreground and background colors for active/inactive cells. Either */ /* the user provided an index into the pre-defined color_list[] or a */ /* random entry from that same array should be selected. */ - size_t color_index = get_integer_resource(state->dpy, "color-index", "Integer"); + color_index = get_integer_resource(state->dpy, "color-index", "Integer"); if (color_index == -1) { color_index = random() % sizeof(color_list)/sizeof(color_list[0]); } else if (color_index >= sizeof(color_list)/sizeof(color_list[0])) { fprintf(stderr, "WARNING: Color index out of range.\n"); color_index = 0; } - XColor fg, bg; fg.red = color_list[color_index].fg_red; fg.green = color_list[color_index].fg_green; fg.blue = color_list[color_index].fg_blue; bg.red = color_list[color_index].bg_red; bg.green = color_list[color_index].bg_green; bg.blue = color_list[color_index].bg_blue; - /* TODO: Since I 'alloc', presumably I must also 'free' these colors */ - /* at some point. Where/how? I don't want to eventually crash my */ - /* X server after months of use. */ XAllocColor(state->dpy, xgwa.colormap, &fg); XAllocColor(state->dpy, xgwa.colormap, &bg); state->fg = gcv.foreground = fg.pixel; @@ -307,14 +323,14 @@ WolframAutomata_init(Display * dpy, Window win) state->gc = XCreateGC(state->dpy, state->win, GCForeground, &gcv); /* Set the size of each simulated cell to NxN pixels for cell_size=N. */ - if (get_boolean_resource(state->dpy, "random-pixel-size", "Boolean")) { + if (get_boolean_resource(state->dpy, "random-cell-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->cell_size = 1 << (random() % 6); } else { - state->cell_size = get_integer_resource(state->dpy, "pixel-size", "Integer"); + state->cell_size = get_integer_resource(state->dpy, "cell-size", "Integer"); } if (state->cell_size < 1) state->cell_size = 1; if (state->cell_size > state->dpy_width) state->cell_size = state->dpy_width; @@ -353,13 +369,13 @@ WolframAutomata_init(Display * dpy, Window win) /* 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"); + state->delay_microsec = get_integer_resource(state->dpy, "delay", "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")) { + if (get_boolean_resource(state->dpy, "random-length", "Boolean")) { /* By empirical observation, keep the product */ /* state->num_generations * state->cell_size */ /* below 10,000 to avoid BadAlloc errors from the X server due to */ @@ -372,7 +388,7 @@ WolframAutomata_init(Display * dpy, Window win) state->num_generations = (state->dpy_height / state->cell_size) + 1; } } else { - state->num_generations = get_integer_resource(state->dpy, "num-generations", "Integer"); + state->num_generations = get_integer_resource(state->dpy, "length", "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 */ @@ -387,21 +403,19 @@ WolframAutomata_init(Display * dpy, Window win) } /* Time to figure out which rule to use for this simulation. */ - /* We ignore any weirdness resulting from the following cast since every */ + /* We ignore any weirdness resulting from the following casts since every */ /* bit pattern is also a valid rule; if the user provides weird input, */ /* then we'll return weird (but well-defined!) output. */ - state->rule_requested = (uint8_t) get_integer_resource(state->dpy, "rule-requested", "Integer"); - state->rule_random = get_boolean_resource(state->dpy, "rule-random", "Boolean"); + state->requested_rule = get_integer_resource(state->dpy, "rule", "Integer"); + state->random_rule = get_boolean_resource(state->dpy, "random-rule", "Boolean"); /* Through the following set of branches, we enforce CLI flag precedence. */ - if (state->rule_random) { + if (state->random_rule) { /* If this flag is set, the user wants truly random rules rather than */ /* random rules from a curated list. */ state->rule_number = (uint8_t) random(); - } else if (state->rule_requested != 0) { - /* Rule 0 is terribly uninteresting, so we are reusing it as a 'null' */ - /* value and hoping nobody notices. Finding a non-zero value means */ - /* the user requested a specific rule. Use it. */ - state->rule_number = state->rule_requested; + } else if (state->requested_rule != -1) { + /* The user requested a specific rule. Use it. */ + state->rule_number = (uint8_t) state->requested_rule; } else { /* No command-line options were specified, so select rules randomly */ /* from a curated list. */ @@ -411,9 +425,6 @@ WolframAutomata_init(Display * dpy, Window win) } /* Time to construct the seed generation for this simulation. */ - state->population_single = get_boolean_resource(state->dpy, "population-single", "Boolean"); - state->population_density = get_integer_resource(state->dpy, "population-density", "Integer"); - if (state->population_density < 0 || state->population_density > 100) state->population_density = 50; state->current_generation = calloc(1, sizeof(*state->current_generation)*state->number_of_cells); if (!state->current_generation) { fprintf(stderr, "ERROR: Failed to calloc() for cell generation in WolframAutomata_init().\n"); @@ -424,7 +435,7 @@ WolframAutomata_init(Display * dpy, Window win) /* setting the seed generation, instead drawing that information from */ /* the curated ruleset. */ switch (curated_ruleset->seed) { - case random_cell: generate_random_seed(state); break; + case random_cell: randomize_seed_density(state); generate_random_seed(state); break; case middle_cell: state->current_generation[state->number_of_cells/2] = True; break; case edge_cell : state->current_generation[0] = True; break; } @@ -432,9 +443,18 @@ WolframAutomata_init(Display * dpy, Window win) /* If we're not using a curated ruleset, process any relevant flags */ /* from the user, falling back to a random seed generation if nothing */ /* else is specified. */ - if (state->population_single) { + if (get_boolean_resource(state->dpy, "seed-left", "Boolean")) { state->current_generation[0] = True; + } else if (get_boolean_resource(state->dpy, "seed-center", "Boolean")) { + state->current_generation[state->number_of_cells/2] = True; + } else if (get_boolean_resource(state->dpy, "seed-right", "Boolean")) { + state->current_generation[state->number_of_cells-1] = True; + } else if (get_integer_resource(state->dpy, "seed-density", "Integer") != -1) { + state->seed_density = get_integer_resource(state->dpy, "seed-density", "Integer"); + if (state->seed_density < 0 || state->seed_density > 100) state->seed_density = 50; + generate_random_seed(state); } else { + randomize_seed_density(state); generate_random_seed(state); } } @@ -442,7 +462,6 @@ WolframAutomata_init(Display * dpy, Window win) state->evolution_history = XCreatePixmap(state->dpy, state->win, state->dpy_width, state->num_generations*state->cell_size, xgwa.depth); /* Pixmap contents are undefined after creation. Explicitly set a black */ /* background by drawing a black rectangle over the entire pixmap. */ - 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->dpy_width, state->num_generations*state->cell_size); @@ -461,13 +480,18 @@ WolframAutomata_draw(Display * dpy, Window win, void * closure) int window_y_offset; /* Calculate and record new generation. */ - Bool new_generation[state->dpy_width]; + Bool * new_generation = malloc(state->dpy_width * sizeof(Bool)); + if (new_generation == NULL) { + fprintf(stderr, "ERROR: Failed to malloc() when calculating new generation.\n"); + exit(EXIT_FAILURE); + } for (xpos = 0; xpos < state->number_of_cells; xpos++) { new_generation[xpos] = calculate_cell(state, xpos); } for (xpos = 0; xpos < state->number_of_cells; xpos++) { state->current_generation[xpos] = new_generation[xpos]; } + free(new_generation); render_current_generation(state); /* Check for end of simulation. */ @@ -501,51 +525,69 @@ WolframAutomata_draw(Display * dpy, Window win, void * closure) return state->delay_microsec; } +static void +WolframAutomata_reshape(Display * dpy, Window win, void * closure, unsigned int w, unsigned int h) +{ + struct state * state = closure; + XWindowAttributes xgwa; + XGetWindowAttributes(state->dpy, state->win, &xgwa); + + /* Only restart the simulation if the window changed size. */ + if (state->dpy_width != xgwa.width || state->dpy_height != xgwa.height) { + WolframAutomata_free(dpy, win, closure); + closure = WolframAutomata_init(dpy, win); + } +} + static const char * WolframAutomata_defaults[] = { - "*delay-usec: 25000", "*admiration-delay: 5", - "*num-generations: 5000", - "*pixel-size: 2", + "*color-index: -1", - "*population-density: 50", - "*population-single: False", - "*random-cellsize: False", + + "*cell-size: 2", + "*random-cell-size: False", + + "*delay: 25000", "*random-delay: False", + + "*length: 5000", "*random-length: False", + + "*rule: -1", "*random-rule: False", - "*rule-requested: 0", + + "*seed-density: -1", + "*seed-left: False", + "*seed-center: False", + "*seed-right: False", + 0 }; static XrmOptionDescRec WolframAutomata_options[] = { - { "-delay-usec", ".delay-usec", XrmoptionSepArg, 0 }, { "-admiration-delay", ".admiration-delay", XrmoptionSepArg, 0 }, - { "-num-generations", ".num-generations", XrmoptionSepArg, 0 }, - { "-pixel-size", ".pixel-size", XrmoptionSepArg, 0 }, + { "-color-index", ".color-index", XrmoptionSepArg, 0 }, - { "-population-density", ".population-density", XrmoptionSepArg, 0 }, - { "-population-single", ".population-single", XrmoptionNoArg, "True" }, - { "-random-cellsize", ".random-pixel-size", XrmoptionNoArg, "True" }, + + { "-cell-size", ".cell-size", XrmoptionSepArg, 0 }, + { "-random-cell-size", ".random-cell-size", XrmoptionNoArg, "True" }, + + { "-delay", ".delay", XrmoptionSepArg, 0 }, { "-random-delay", ".random-delay", XrmoptionNoArg, "True" }, - { "-random-length", ".random-num-generations", XrmoptionNoArg, "True" }, - { "-random-rule", ".rule-random", XrmoptionNoArg, "True" }, - { "-rule", ".rule-requested", XrmoptionSepArg, 0 }, - { 0, 0, 0, 0 } -}; -static void -WolframAutomata_reshape(Display * dpy, Window win, void * closure, unsigned int w, unsigned int h) -{ - struct state * state = closure; - XWindowAttributes xgwa; - XGetWindowAttributes(state->dpy, state->win, &xgwa); + { "-length", ".length", XrmoptionSepArg, 0 }, + { "-random-length", ".random-length", XrmoptionNoArg, "True" }, - /* Only restart the simulation if the window changed size. */ - if (state->dpy_width != xgwa.width || state->dpy_height != xgwa.height) { - WolframAutomata_free(dpy, win, closure); - closure = WolframAutomata_init(dpy, win); - } -} + { "-rule", ".rule", XrmoptionSepArg, 0 }, + { "-random-rule", ".random-rule", XrmoptionNoArg, "True" }, + + { "-seed-density", ".seed-density", XrmoptionSepArg, 0 }, + { "-seed-left", ".seed-left", XrmoptionNoArg, "True" }, + { "-seed-center", ".seed-center", XrmoptionNoArg, "True" }, + { "-seed-right", ".seed-right", XrmoptionNoArg, "True" }, + + { 0, 0, 0, 0 } +}; XSCREENSAVER_MODULE ("1D Nearest-Neighbor Cellular Automata", WolframAutomata)