| 1 | /* (c) 2021 Aaron Taylor <ataylor at subgeniuskitty dot com> */ |
| 2 | /* See LICENSE.txt file for copyright and license details. */ |
| 3 | |
| 4 | |
| 5 | /* TODO: Write description explaining that this simulates all 1D NN CAs, and explain briefly what all those terms imply. */ |
| 6 | /* TODO: Explain things like the topology of the space. */ |
| 7 | /* TODO: Explain how the numbering for a CA expands to the actual rules. */ |
| 8 | /* TODO: Briefly explain the four different classes of behavior and their implications. */ |
| 9 | /* TODO: Include a link to Wikipedia. */ |
| 10 | /* TODO: I suppose a lot of this stuff goes in the README instead. */ |
| 11 | /* TODO: Explain the data structures in detail. */ |
| 12 | /* TODO: Explain all the options, like the various starting conditions. */ |
| 13 | /* TODO: Explain all the dependencies like libXpm. */ |
| 14 | |
| 15 | |
| 16 | /* TODO: Add a #define for the hack version. */ |
| 17 | /* TODO: Check manpage for all functions I use and ensure my includes are correct. I don't want to depend on picking up includes via screenhack.h. */ |
| 18 | /* TODO: Verify everything in this file is C89. Get rid of things like '//' comments, pack all my declarations upfront, no stdint, etc. */ |
| 19 | |
| 20 | #include <X11/Intrinsic.h> |
| 21 | #include "screenhack.h" |
| 22 | |
| 23 | /* |
| 24 | * We do a few manual manipulations of X resources in this hack, like picking |
| 25 | * random colors. In order to ensure our manual manipulations always use the |
| 26 | * same X resource specification as Xscreensaver, we pass HACKNAME to |
| 27 | * Xscreensaver via the XSCREENSAVER_MODULE() line at the bottom of this file, |
| 28 | * and then always use HACKNAME or MAKE_STRING(HACKNAME) as the base of the |
| 29 | * resource specification when making manual manipulations. |
| 30 | */ |
| 31 | #define HACKNAME WolframAutomata |
| 32 | #define MAKE_STRING_X(s) #s |
| 33 | #define MAKE_STRING(s) MAKE_STRING_X(s) |
| 34 | |
| 35 | // Command line options |
| 36 | // directory to output XBM files of each run (and call an external command to convert to PNGs?) |
| 37 | // -save-dir STRING |
| 38 | // (could use libXpm to save an XPM and then convert to PNG with ImageMagick) (this is a single function call to go from pixmap -> file) |
| 39 | // (since it depends on an external library, make this whole feature optional at build-time?) |
| 40 | // number of generations to simulate |
| 41 | // -random-generations |
| 42 | // -num-generations N |
| 43 | // delay time (speed of simulation) |
| 44 | // -random-delay |
| 45 | // -delay-usec N |
| 46 | // foreground and background color |
| 47 | // -random-colors (highest precedence) |
| 48 | // -foreground "COLORNAME" |
| 49 | // -background "COLORNAME" |
| 50 | // (default is black and white) |
| 51 | // (mention sample color combinations in manpage, and link to: https://en.wikipedia.org/wiki/X11_color_names) |
| 52 | // (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.) |
| 53 | // display info overlay with CA number and start conditions? |
| 54 | // -overlay |
| 55 | // which ruleset number to use? Or random? Or random from small set of hand-selected interesting examples? |
| 56 | // In order of precedence: |
| 57 | // -rule-random (select a random rule on each run) |
| 58 | // -rule N (always simulate Rule N on each run) |
| 59 | // (if neither of the above two are specified, then a random CURATED rule is selected on each run) |
| 60 | // which starting population to use, random or one bit? (for random: allow specifying a density) |
| 61 | // In order of precedence: |
| 62 | // -population-single |
| 63 | // -population-random DENSITY |
| 64 | // (the two options above only apply to the simulation under the -rule-random or -rule N options. in curated mode, starting population is defined in the curation array) |
| 65 | // TODO: In the future, add the option for user to pass list of cell IDs to turn ON. |
| 66 | // size of pixel square (e.g. 1x1, 2x2, 3x3, etc) |
| 67 | // -random-pixel-size |
| 68 | // -pixel-size N |
| 69 | |
| 70 | /* -------------------------------------------------------------------------- */ |
| 71 | /* Data Structures */ |
| 72 | /* -------------------------------------------------------------------------- */ |
| 73 | |
| 74 | struct state { |
| 75 | /* Various X resources */ |
| 76 | Display * dpy; |
| 77 | Window win; |
| 78 | GC gc; |
| 79 | |
| 80 | // TODO: Explain that this holds the whole evolution of the CA and the actual displayed visualization is simply a snapshot into this pixmap. |
| 81 | Pixmap evolution_history; |
| 82 | |
| 83 | // TODO: Explain all of these. |
| 84 | unsigned long fg, bg; |
| 85 | int xlim, ylim, ypos; // explain roughly how and where we use these. Note: I'm not thrilled xlim/ylim since they are actually the width of the display, not the limit of the index (off by one). Change those names. |
| 86 | Bool display_info; |
| 87 | |
| 88 | Bool * current_generation; |
| 89 | |
| 90 | // TODO: Describe these. |
| 91 | uint8_t rule_number; // Note: This is not a CLI option. You're thinking of rule_requested. |
| 92 | uint8_t rule_requested; // Note: Repurposing Rule 0 as a null value. |
| 93 | Bool rule_random; |
| 94 | |
| 95 | // TODO: Describe these. |
| 96 | int population_density; |
| 97 | Bool population_single; |
| 98 | |
| 99 | /* Misc Commandline Options */ |
| 100 | int pixel_size; /* Size of CA cell in pixels (e.g. pixel_size=3 means 3x3 pixels per cell). */ |
| 101 | int delay_microsec; /* Requested delay to screenhack framework before next call to WolframAutomata_draw(). */ |
| 102 | int num_generations; /* Number of generations of the CA to simulate before restarting. */ |
| 103 | |
| 104 | /* Expository Variables - Not strictly necessary, but makes some code easier to read. */ |
| 105 | size_t number_of_cells; |
| 106 | }; |
| 107 | |
| 108 | // TODO: Decorations |
| 109 | enum seed_population { |
| 110 | random_cell, |
| 111 | middle_cell, |
| 112 | edge_cell |
| 113 | }; |
| 114 | |
| 115 | // TODO: Decorations |
| 116 | struct curated_ruleset { |
| 117 | uint8_t rule; |
| 118 | enum seed_population seed; |
| 119 | }; |
| 120 | |
| 121 | // TODO: Decorations |
| 122 | static const struct curated_ruleset curated_ruleset_list[] = { |
| 123 | {18, middle_cell}, |
| 124 | {30, middle_cell}, |
| 125 | {45, middle_cell}, |
| 126 | {54, middle_cell}, |
| 127 | {57, middle_cell}, |
| 128 | {73, middle_cell}, |
| 129 | {105, middle_cell}, |
| 130 | {109, middle_cell}, |
| 131 | {129, middle_cell}, |
| 132 | {133, middle_cell}, |
| 133 | {135, middle_cell}, |
| 134 | {150, middle_cell}, |
| 135 | {30, edge_cell}, |
| 136 | {45, edge_cell}, |
| 137 | {57, edge_cell}, |
| 138 | {60, edge_cell}, |
| 139 | {75, edge_cell}, |
| 140 | {107, edge_cell}, |
| 141 | {110, edge_cell}, |
| 142 | {133, edge_cell}, |
| 143 | {137, edge_cell}, |
| 144 | {169, edge_cell}, |
| 145 | {225, edge_cell}, |
| 146 | {22, random_cell}, |
| 147 | {30, random_cell}, |
| 148 | {54, random_cell}, |
| 149 | {62, random_cell}, |
| 150 | {90, random_cell}, |
| 151 | {105, random_cell}, |
| 152 | {108, random_cell}, |
| 153 | {110, random_cell}, |
| 154 | {126, random_cell}, |
| 155 | {146, random_cell}, |
| 156 | {150, random_cell}, |
| 157 | {182, random_cell}, |
| 158 | {184, random_cell}, |
| 159 | {225, random_cell}, |
| 160 | {240, random_cell} |
| 161 | }; |
| 162 | |
| 163 | // TODO: Decorations |
| 164 | struct color_pair { |
| 165 | char * fg; |
| 166 | char * bg; |
| 167 | }; |
| 168 | |
| 169 | // TODO: Decorations |
| 170 | static const struct color_pair color_list[] = { |
| 171 | {"red", "black"}, |
| 172 | {"olive", "black"}, |
| 173 | {"teal", "black"}, |
| 174 | {"slateblue", "black"}, |
| 175 | {"violet", "black"}, |
| 176 | {"purple", "black"}, |
| 177 | {"white", "black"}, |
| 178 | {"white", "darkgreen"}, |
| 179 | {"white", "darkmagenta"}, |
| 180 | {"white", "darkred"}, |
| 181 | {"white", "darkblue"}, |
| 182 | {"darkslategray", "darkslategray1"}, |
| 183 | {"lightsteelblue", "darkslategray"}, |
| 184 | {"royalblue4", "royalblue"}, |
| 185 | {"antiquewhite2", "antiquewhite4"}, |
| 186 | {"darkolivegreen1", "darkolivegreen"}, |
| 187 | {"darkseagreen1", "darkseagreen4"}, |
| 188 | {"pink", "darkred"}, |
| 189 | {"lightblue", "darkgreen"}, |
| 190 | {"red", "blue"}, |
| 191 | {"red", "darkgreen"}, |
| 192 | {"aqua", "teal"}, |
| 193 | {"darkblue", "teal"}, |
| 194 | {"khaki", "seagreen"}, |
| 195 | {"khaki", "darkolivegreen"}, |
| 196 | {"lightslategray", "darkslategray"}, |
| 197 | {"tomato", "darkslategray"}, |
| 198 | {"tomato", "darkcyan"} |
| 199 | }; |
| 200 | |
| 201 | /* -------------------------------------------------------------------------- */ |
| 202 | /* Helper Functions */ |
| 203 | /* -------------------------------------------------------------------------- */ |
| 204 | |
| 205 | // TODO: decorations? inline? |
| 206 | void |
| 207 | generate_random_seed(struct state * state) |
| 208 | { |
| 209 | int i; |
| 210 | for (i = 0; i < state->number_of_cells; i++) { |
| 211 | state->current_generation[i] = ((random() % 100) < state->population_density) ? True : False; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | // TODO: function decorations? |
| 216 | // TODO: Explain why this santizes the index for accessing current_generation (i.e. it creates a circular topology). |
| 217 | size_t |
| 218 | sindex(struct state * state, int index) |
| 219 | { |
| 220 | while (index < 0) { |
| 221 | index += state->number_of_cells; |
| 222 | } |
| 223 | while (index >= state->number_of_cells) { |
| 224 | index -= state->number_of_cells; |
| 225 | } |
| 226 | return (size_t) index; |
| 227 | } |
| 228 | |
| 229 | // TODO: function decorations? |
| 230 | // TODO: At least give a one-sentence explanation of the algorithm since this function is the core of the simulation. |
| 231 | Bool |
| 232 | calculate_cell(struct state * state, int cell_id) |
| 233 | { |
| 234 | uint8_t cell_pattern = 0; |
| 235 | int i; |
| 236 | for (i = -1; i < 2; i++) { |
| 237 | cell_pattern = cell_pattern << 1; |
| 238 | if (state->current_generation[sindex(state, cell_id+i)] == True) { |
| 239 | cell_pattern |= 1; |
| 240 | } |
| 241 | } |
| 242 | if ((state->rule_number >> cell_pattern) & 1) { |
| 243 | return True; |
| 244 | } else { |
| 245 | return False; |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | // TODO: function decorations? |
| 250 | void |
| 251 | render_current_generation(struct state * state) |
| 252 | { |
| 253 | size_t xpos; |
| 254 | for (xpos = 0; xpos < state->number_of_cells; xpos++) { |
| 255 | if (state->current_generation[xpos] == True) { |
| 256 | XFillRectangle(state->dpy, state->evolution_history, state->gc, xpos*state->pixel_size, state->ypos, state->pixel_size, state->pixel_size); |
| 257 | } else { |
| 258 | XSetForeground(state->dpy, state->gc, state->bg); |
| 259 | XFillRectangle(state->dpy, state->evolution_history, state->gc, xpos*state->pixel_size, state->ypos, state->pixel_size, state->pixel_size); |
| 260 | XSetForeground(state->dpy, state->gc, state->fg); |
| 261 | } |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | /* -------------------------------------------------------------------------- */ |
| 266 | /* Screenhack API Functions */ |
| 267 | /* -------------------------------------------------------------------------- */ |
| 268 | |
| 269 | static void * |
| 270 | WolframAutomata_init(Display * dpy, Window win) |
| 271 | { |
| 272 | struct state * state = calloc(1, sizeof(*state)); |
| 273 | if (!state) { |
| 274 | fprintf(stderr, "ERROR: Failed to calloc() for state struct in WolframAutomata_init().\n"); |
| 275 | exit(EXIT_FAILURE); |
| 276 | } |
| 277 | |
| 278 | XGCValues gcv; |
| 279 | XWindowAttributes xgwa; |
| 280 | const struct curated_ruleset * curated_ruleset = NULL; |
| 281 | |
| 282 | state->dpy = dpy; |
| 283 | state->win = win; |
| 284 | |
| 285 | XGetWindowAttributes(state->dpy, state->win, &xgwa); |
| 286 | state->xlim = xgwa.width; |
| 287 | state->ylim = xgwa.height; |
| 288 | state->ypos = 0; // TODO: Explain why. |
| 289 | |
| 290 | if (get_boolean_resource(state->dpy, "random-colors", "Boolean")) { |
| 291 | XrmDatabase db = XtDatabase(state->dpy); |
| 292 | size_t rand_i = random() % sizeof(color_list)/sizeof(color_list[0]); |
| 293 | XrmPutStringResource(&db, MAKE_STRING(HACKNAME) ".background", color_list[rand_i].bg); |
| 294 | XrmPutStringResource(&db, MAKE_STRING(HACKNAME) ".foreground", color_list[rand_i].fg); |
| 295 | } |
| 296 | |
| 297 | state->fg = gcv.foreground = get_pixel_resource(state->dpy, xgwa.colormap, "foreground", "Foreground"); |
| 298 | state->bg = gcv.background = get_pixel_resource(state->dpy, xgwa.colormap, "background", "Background"); |
| 299 | state->gc = XCreateGC(state->dpy, state->win, GCForeground, &gcv); |
| 300 | |
| 301 | state->delay_microsec = get_integer_resource(state->dpy, "delay-usec", "Integer"); |
| 302 | if (state->delay_microsec < 0) state->delay_microsec = 0; |
| 303 | |
| 304 | state->pixel_size = get_integer_resource(state->dpy, "pixel-size", "Integer"); |
| 305 | if (state->pixel_size < 1) state->pixel_size = 1; |
| 306 | if (state->pixel_size > state->xlim) state->pixel_size = state->xlim; |
| 307 | |
| 308 | state->number_of_cells = state->xlim / state->pixel_size; |
| 309 | // TODO: Do we want to enforce that number_of_cells > 0? |
| 310 | |
| 311 | /* The minimum number of generations is 2 since we must allocate enough */ |
| 312 | /* space to hold the seed generation and at least one pass through */ |
| 313 | /* WolframAutomata_draw(), which is where we check whether or not we've */ |
| 314 | /* reached the end of the pixmap. */ |
| 315 | state->num_generations = get_integer_resource(state->dpy, "num-generations", "Integer"); |
| 316 | if (state->num_generations < 0) state->num_generations = 2; |
| 317 | |
| 318 | /* Time to figure out which rule to use for this simulation. */ |
| 319 | /* We ignore any weirdness resulting from the following cast since every */ |
| 320 | /* bit pattern is also a valid rule; if the user provides weird input, */ |
| 321 | /* then we'll return weird (but well-defined!) output. */ |
| 322 | state->rule_requested = (uint8_t) get_integer_resource(state->dpy, "rule-requested", "Integer"); |
| 323 | state->rule_random = get_boolean_resource(state->dpy, "rule-random", "Boolean"); |
| 324 | /* Through the following set of branches, we enforce CLI flag precedence. */ |
| 325 | if (state->rule_random) { |
| 326 | /* If this flag is set, the user wants truly random rules rather than */ |
| 327 | /* random rules from a curated list. */ |
| 328 | state->rule_number = (uint8_t) random(); |
| 329 | } else if (state->rule_requested != 0) { |
| 330 | /* Rule 0 is terribly uninteresting, so we are reusing it as a 'null' */ |
| 331 | /* value and hoping nobody notices. Finding a non-zero value means */ |
| 332 | /* the user requested a specific rule. Use it. */ |
| 333 | state->rule_number = state->rule_requested; |
| 334 | } else { |
| 335 | /* No command-line options were specified, so select rules randomly */ |
| 336 | /* from a curated list. */ |
| 337 | size_t number_of_array_elements = sizeof(curated_ruleset_list)/sizeof(curated_ruleset_list[0]); |
| 338 | curated_ruleset = &curated_ruleset_list[random() % number_of_array_elements]; |
| 339 | state->rule_number = curated_ruleset->rule; |
| 340 | } |
| 341 | |
| 342 | /* Time to construct the seed generation for this simulation. */ |
| 343 | state->population_single = get_boolean_resource(state->dpy, "population-single", "Boolean"); |
| 344 | state->population_density = get_integer_resource(state->dpy, "population-density", "Integer"); |
| 345 | if (state->population_density < 0 || state->population_density > 100) state->population_density = 50; |
| 346 | state->current_generation = calloc(1, sizeof(*state->current_generation)*state->number_of_cells); |
| 347 | if (!state->current_generation) { |
| 348 | fprintf(stderr, "ERROR: Failed to calloc() for cell generation in WolframAutomata_init().\n"); |
| 349 | exit(EXIT_FAILURE); |
| 350 | } |
| 351 | if (curated_ruleset) { |
| 352 | /* If we're using a curated ruleset, ignore any CLI flags related to */ |
| 353 | /* setting the seed generation, instead drawing that information from */ |
| 354 | /* the curated ruleset. */ |
| 355 | switch (curated_ruleset->seed) { |
| 356 | case random_cell: generate_random_seed(state); break; |
| 357 | case middle_cell: state->current_generation[state->number_of_cells/2] = True; break; |
| 358 | case edge_cell : state->current_generation[0] = True; break; |
| 359 | } |
| 360 | } else { |
| 361 | /* If we're not using a curated ruleset, process any relevant flags */ |
| 362 | /* from the user, falling back to a random seed generation if nothing */ |
| 363 | /* else is specified. */ |
| 364 | if (state->population_single) { |
| 365 | state->current_generation[0] = True; |
| 366 | } else { |
| 367 | generate_random_seed(state); |
| 368 | } |
| 369 | } |
| 370 | |
| 371 | // TODO: These should be command-line options, but I need to learn how the get_integer_resource() and similar functions work first. |
| 372 | state->display_info = True; |
| 373 | |
| 374 | state->evolution_history = XCreatePixmap(state->dpy, state->win, state->xlim, state->num_generations*state->pixel_size, xgwa.depth); |
| 375 | // Pixmap contents are undefined after creation. Explicitly set a black |
| 376 | // background by drawing a black rectangle over the entire pixmap. |
| 377 | XColor blackx, blacks; |
| 378 | XAllocNamedColor(state->dpy, DefaultColormapOfScreen(DefaultScreenOfDisplay(state->dpy)), "black", &blacks, &blackx); |
| 379 | XSetForeground(state->dpy, state->gc, blacks.pixel); |
| 380 | XFillRectangle(state->dpy, state->evolution_history, state->gc, 0, 0, state->xlim, state->num_generations*state->pixel_size); |
| 381 | XSetForeground(state->dpy, state->gc, state->fg); |
| 382 | render_current_generation(state); |
| 383 | state->ypos += state->pixel_size; |
| 384 | |
| 385 | return state; |
| 386 | } |
| 387 | |
| 388 | static unsigned long |
| 389 | WolframAutomata_draw(Display * dpy, Window win, void * closure) |
| 390 | { |
| 391 | // TODO: Mark these basic sections of the function |
| 392 | //draw() |
| 393 | // calculate (and store) new generation |
| 394 | // draw new generation as line of pixels on pixmap |
| 395 | // calculate current 'viewport' into pixmap |
| 396 | // display on screen |
| 397 | // check for termination condition |
| 398 | |
| 399 | struct state * state = closure; |
| 400 | int xpos; |
| 401 | int window_y_offset; |
| 402 | |
| 403 | Bool new_generation[state->xlim]; |
| 404 | for (xpos = 0; xpos < state->number_of_cells; xpos++) { |
| 405 | new_generation[xpos] = calculate_cell(state, xpos); |
| 406 | } |
| 407 | for (xpos = 0; xpos < state->number_of_cells; xpos++) { |
| 408 | state->current_generation[xpos] = new_generation[xpos]; |
| 409 | } |
| 410 | render_current_generation(state); |
| 411 | |
| 412 | // Was this the final generation of this particular simulation? If so, give |
| 413 | // the user a moment to bask in the glory of our output and then start a |
| 414 | // new simulation. |
| 415 | if (state->ypos/state->pixel_size < state->num_generations-1) { |
| 416 | state->ypos += state->pixel_size; |
| 417 | } else { |
| 418 | // TODO: Wait for a second or two, clear the screen and do a new iteration with suitably changed settings. |
| 419 | // Note: Since we can't actually loop or sleep here, we need to add a flag to the state struct to indicate that we're in an 'admiration timewindow' (and indicate when it should end) |
| 420 | printf("infinite hamster wheel\n"); |
| 421 | while (1) continue; |
| 422 | } |
| 423 | |
| 424 | // Calculate the vertical offset of the current 'window' into the history |
| 425 | // of the CA. After the CA's evolution extends past what we can display, have |
| 426 | // the window track the current generation and most recent history. |
| 427 | if (state->ypos < state->ylim) { |
| 428 | window_y_offset = 0; |
| 429 | } else { |
| 430 | window_y_offset = state->ypos - (state->ylim - 1); |
| 431 | } |
| 432 | |
| 433 | // Render everything to the display. |
| 434 | XCopyArea(state->dpy, state->evolution_history, state->win, state->gc, 0, window_y_offset, state->xlim, state->ylim, 0, 0); |
| 435 | // TODO: Print info on screen if display_info is true. Will need fonts/etc. Do I want to create a separate pixmap for this during the init() function and then just copy the pixmap each time we draw the screen in draw()? |
| 436 | |
| 437 | return state->delay_microsec; |
| 438 | } |
| 439 | |
| 440 | // TODO: Fix formatting |
| 441 | static const char * WolframAutomata_defaults[] = { |
| 442 | ".background: black", |
| 443 | ".foreground: white", |
| 444 | "*random-colors: False", |
| 445 | "*delay-usec: 25000", |
| 446 | // TODO: Difference between dot and asterisk? Presumably the asterisk matches all resouces of attribute "pixelsize"? Apply answer to all new options. |
| 447 | "*pixel-size: 2", |
| 448 | "*num-generations: 5000", |
| 449 | "*rule-requested: 0", |
| 450 | "*rule-random: False", |
| 451 | "*population-density: 50", |
| 452 | "*population-single: False", |
| 453 | 0 |
| 454 | }; |
| 455 | |
| 456 | // TODO: Fix formatting |
| 457 | static XrmOptionDescRec WolframAutomata_options[] = { |
| 458 | { "-background", ".background", XrmoptionSepArg, 0}, |
| 459 | { "-foreground", ".foreground", XrmoptionSepArg, 0}, |
| 460 | { "-random-colors", ".random-colors", XrmoptionNoArg, "True"}, |
| 461 | { "-delay-usec", ".delay-usec", XrmoptionSepArg, 0 }, |
| 462 | { "-pixel-size", ".pixel-size", XrmoptionSepArg, 0 }, |
| 463 | { "-num-generations", ".num-generations", XrmoptionSepArg, 0 }, |
| 464 | { "-rule", ".rule-requested", XrmoptionSepArg, 0 }, |
| 465 | { "-rule-random", ".rule-random", XrmoptionNoArg, "True" }, |
| 466 | { "-population-density", ".population-density", XrmoptionSepArg, 0 }, |
| 467 | { "-population-single", ".population-single", XrmoptionNoArg, "True" }, |
| 468 | { 0, 0, 0, 0 } |
| 469 | }; |
| 470 | |
| 471 | static Bool |
| 472 | WolframAutomata_event(Display * dpy, Window win, void * closure, XEvent * event) |
| 473 | { |
| 474 | return False; |
| 475 | } |
| 476 | |
| 477 | static void |
| 478 | WolframAutomata_free(Display * dpy, Window win, void * closure) |
| 479 | { |
| 480 | struct state * state = closure; |
| 481 | XFreeGC(state->dpy, state->gc); |
| 482 | XFreePixmap(state->dpy, state->evolution_history); |
| 483 | free(state->current_generation); |
| 484 | free(state); |
| 485 | } |
| 486 | |
| 487 | static void |
| 488 | WolframAutomata_reshape(Display * dpy, Window win, void * closure, unsigned int w, unsigned int h) |
| 489 | { |
| 490 | WolframAutomata_free(dpy, win, closure); |
| 491 | closure = WolframAutomata_init(dpy, win); |
| 492 | } |
| 493 | |
| 494 | XSCREENSAVER_MODULE ("1D Nearest-Neighbor Cellular Automata", HACKNAME) |
| 495 | |