/* (c) 2021 Aaron Taylor <ataylor at subgeniuskitty dot com> */
/* 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. */
// 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%?)
/* Various X resources */
// 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
;
// TODO: Explain all of these.
int delay_microsec
; // per generation
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.
// 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
;
WolframAutomata_init(Display
* dpy
, Window win
)
struct state
* state
= calloc(1, sizeof(*state
)); // TODO: Check calloc() call
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
->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.
// TODO: function decorations?
// TODO: Explain why this santizes the index for accessing current_generation (i.e. it creates a circular topology).
sindex(struct state
* state
, int index
)
while (index
>= state
->xlim
) {
// TODO: function decorations?
// TODO: At least give a one-sentence explanation of the algorithm since this function is the core of the simulation.
calculate_cell(struct state
* state
, int cell_id
)
uint8_t cell_pattern
= 0;
for (i
= -1; i
< 2; i
++) {
cell_pattern
= cell_pattern
<< 1;
if (state
->current_generation
[sindex(state
, cell_id
+i
)] == True
) {
if ((state
->ruleset
>> cell_pattern
) & 1) {
// TODO: function decorations?
render_current_generation(struct state
* state
)
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);
WolframAutomata_draw(Display
* dpy
, Window win
, void * closure
)
// TODO: Mark these basic sections of the function
// calculate (and store) new generation
// draw new generation as line of pixels on pixmap
// calculate current 'viewport' into pixmap
// check for termination condition
struct state
* state
= closure
;
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
if (state
->ypos
< state
->num_generations
-1) {
// 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)
// 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
= 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
[] = {
static XrmOptionDescRec WolframAutomata_options
[] = {
{ "-delay", ".delay", XrmoptionSepArg
, 0 },
WolframAutomata_event(Display
* dpy
, Window win
, void * closure
, XEvent
* event
)
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
);
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
)