From 0c731d4acbd9e19b56346f5eacedf37710b862e1 Mon Sep 17 00:00:00 2001 From: Aaron Taylor Date: Fri, 12 Mar 2021 16:07:29 -0800 Subject: [PATCH] Initial commit of WolframAutomata hack. Basic functionality is in place; program can render an arbitrary CA on the screen. --- hacks/WolframAutomata/WolframAutomata.c | 227 ++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 hacks/WolframAutomata/WolframAutomata.c diff --git a/hacks/WolframAutomata/WolframAutomata.c b/hacks/WolframAutomata/WolframAutomata.c new file mode 100644 index 0000000..174e7d1 --- /dev/null +++ b/hacks/WolframAutomata/WolframAutomata.c @@ -0,0 +1,227 @@ +/* (c) 2021 Aaron Taylor */ +/* See LICENSE.txt file for copyright and license details. */ + + +/* TODO: Write description explaining that this simulates all 1D NN CAs, and explain briefly what all those terms imply. */ +/* TODO: Explain things like the topology of the space. */ +/* TODO: Explain how the numbering for a CA expands to the actual rules. */ +/* TODO: Briefly explain the four different classes of behavior and their implications. */ +/* TODO: Include a link to Wikipedia. */ +/* TODO: I suppose a lot of this stuff goes in the README instead. */ +/* TODO: Explain the data structures in detail. */ +/* TODO: Explain all the options, like the various starting conditions. */ + + +/* 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. */ +/* TODO: Verify everything in this file is C89. Get rid of things like '//' comments, pack all my declarations upfront, no stdint, etc. */ +/* TODO: Tabs -> Spaces before each commit. */ + +#include "screenhack.h" + +// Command line options +// directory to output XBM files of each run (and call an external command to convert to PNGs?) +// number of generations to simulate +// delay time (speed of simulation) +// foreground and background color +// display info overlay with CA number and start conditions? +// which ruleset number to use? Or random? Or random from small set of hand-selected interesting examples? +// which starting population to use? Or random? Or one bit in middle? Or one bit on edge? (For random: Can I allow specifying a density like 25%, 50%, 75%?) + +struct state { + /* Various X resources */ + Display * dpy; + Window win; + GC gc; + + // TODO: Explain that this holds the whole evolution of the CA and the actual displayed visualization is simply a snapshot into this pixmap. + Pixmap evolution_history; + size_t num_generations; + + // TODO: Explain all of these. + int delay_microsec; // per generation + unsigned long fg, bg; + 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. + Bool display_info; + // TODO: Add an option for 'pixel size', so the user can define 1x1 or 2x2 or 3x3 or ... pixels. But then I need to deal with leftover pixels. + + Bool * current_generation; + uint8_t ruleset; +}; + +static void * +WolframAutomata_init(Display * dpy, Window win) +{ + struct state * state = calloc(1, sizeof(*state)); // TODO: Check calloc() call + XGCValues gcv; + XWindowAttributes xgwa; + + state->dpy = dpy; + state->win = win; + + XGetWindowAttributes(state->dpy, state->win, &xgwa); + state->xlim = xgwa.width; + state->ylim = xgwa.height; + state->ypos = 0; // TODO: Explain why. + + state->fg = gcv.foreground = get_pixel_resource(state->dpy, xgwa.colormap, "foreground", "Foreground"); + 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", "Integer"); + if (state->delay_microsec < 0) state->delay_microsec = 0; + + // TODO: These should be command-line options, but I need to learn how the get_integer_resource() and similar functions work first. + state->display_info = True; + state->ruleset = 30; + state->num_generations = 10000; // TODO: Enforce that this is >1 in order to hold the seed generation and at least one pass through WolframAutomata_draw(), which is where we check for a full pixmap. + + state->current_generation = calloc(1, (sizeof(*(state->current_generation))*(state->xlim))); // TODO: Check calloc() call TODO: Can't recall precedence; can I eliminate any parenthesis? + // TODO: Make the starting state a user-configurable option. At least give the user some options like 'random', 'one-middle', 'one edge', etc. + // Ideally accept something like a list of integers representing starting pixels to be "on". + state->current_generation[state->xlim-1] = True; + + state->evolution_history = XCreatePixmap(state->dpy, state->win, state->xlim, state->num_generations, 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); + XFillRectangle(state->dpy, state->evolution_history, state->gc, 0, 0, state->xlim, state->num_generations); + XSetForeground(state->dpy, state->gc, state->fg); + // TODO: Need to draw starting generation on pixmap and increment state->ypos. + + return state; +} + +// TODO: function decorations? +// TODO: Explain why this santizes the index for accessing current_generation (i.e. it creates a circular topology). +size_t +sindex(struct state * state, int index) +{ + while (index < 0) { + index += state->xlim; + } + while (index >= state->xlim) { + index -= state->xlim; + } + return (size_t) index; +} + +// TODO: function decorations? +// TODO: At least give a one-sentence explanation of the algorithm since this function is the core of the simulation. +Bool +calculate_cell(struct state * state, int cell_id) +{ + uint8_t cell_pattern = 0; + int i; + for (i = -1; i < 2; i++) { + cell_pattern = cell_pattern << 1; + if (state->current_generation[sindex(state, cell_id+i)] == True) { + cell_pattern |= 1; + } + } + if ((state->ruleset >> cell_pattern) & 1) { + return True; + } else { + return False; + } +} + +// TODO: function decorations? +void +render_current_generation(struct state * state) +{ + size_t xpos; + for (xpos = 0; xpos < state->xlim; xpos++) { + if (state->current_generation[xpos] == True) { + XFillRectangle(state->dpy, state->evolution_history, state->gc, xpos, state->ypos, 1, 1); + } + } +} + +static unsigned long +WolframAutomata_draw(Display * dpy, Window win, void * closure) +{ +// TODO: Mark these basic sections of the function +//draw() +// calculate (and store) new generation +// draw new generation as line of pixels on pixmap +// calculate current 'viewport' into pixmap +// display on screen +// check for termination condition + + struct state * state = closure; + int xpos; + int window_y_offset; + + Bool new_generation[state->xlim]; + for (xpos = 0; xpos < state->xlim; xpos++) { + new_generation[xpos] = calculate_cell(state, xpos); + } + for (xpos = 0; xpos < state->xlim; xpos++) { + state->current_generation[xpos] = new_generation[xpos]; + } + render_current_generation(state); + + // Was this the final generation of this particular simulation? If so, give + // the user a moment to bask in the glory of our output and then start a + // new simulation. + if (state->ypos < state->num_generations-1) { + state->ypos++; + } else { + // TODO: Wait for a second or two, clear the screen and do a new iteration with suitably changed settings. + // 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) + while (1) continue; + } + + // Calculate the vertical offset of the current 'window' into the history + // of the CA. After the CA's evolution extends past what we can display, have + // the window track the current generation and most recent history. + if (state->ypos < state->ylim) { + window_y_offset = 0; + } else { + window_y_offset = state->ypos - (state->ylim - 1); + } + + // Render everything to the display. + XCopyArea(state->dpy, state->evolution_history, state->win, state->gc, 0, window_y_offset, state->xlim, state->ylim, 0, 0); + // 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()? + + return state->delay_microsec; +} + +static const char * WolframAutomata_defaults[] = { + ".background: black", + ".foreground: white", + "*delay: 2500", + 0 +}; + +static XrmOptionDescRec WolframAutomata_options[] = { + { "-delay", ".delay", XrmoptionSepArg, 0 }, + { 0, 0, 0, 0 } +}; + +static Bool +WolframAutomata_event(Display * dpy, Window win, void * closure, XEvent * event) +{ + return False; +} + +static void +WolframAutomata_free(Display * dpy, Window win, void * closure) +{ + struct state * state = closure; + XFreeGC(state->dpy, state->gc); + XFreePixmap(state->dpy, state->evolution_history); + free(state->current_generation); + free(state); +} + +static void +WolframAutomata_reshape(Display * dpy, Window win, void * closure, unsigned int w, unsigned int h) +{ + WolframAutomata_free(dpy, win, closure); + WolframAutomata_init(dpy, win); +} + +XSCREENSAVER_MODULE ("1D Nearest-Neighbor Cellular Automata", WolframAutomata) + -- 2.20.1