////////////////////////////////////////////////////////////////////////
// CP1521 22T2 — assignment 1: a cellular automaton renderer
//
// This code runs a one-dimensional, three-neighbour cellular automaton.
// It examines its neighbours and its value in the previous generation
// to derive the value for the next generation.
//
// . . . . # . . . .
// . . . # # # . . .
// . . # # . . # . .
// . # # . # # # # .
// # # . . # . . . #
//
// Here, we using ‘#’ to indicate a cell that’s alive; and ‘.’ to
// indicate a cell that is not.
//
// Given we examine three neighbours, there are eight states that the
// prior cells could be in. They are:
//
// . . . . . # . # . . # # # . . # . # # # . # # #
// 0 1 2 3 4 5 6 7
//
// For each one, we decide what action to take. For example, we might
// choose to have the following `rule’:
//
// . . . . . # . # . . # # # . . # . # # # . # # #
// . # # # # . . .
//
// We apply this rule to every cell, to determine whether the next state
// is alive or dead; and this forms the next generation. If we print
// these generations, one after the other, we can get some interesting
// patterns.
//
// The description of the rule above — by example, showing each case
// and how it should be handled — is inefficient. We can abbreviate
// this rule by reading it in binary, considering live cells as 1’s and
// dead cells as 0s; and if we consider the prior states to be a binary
// value too — the above rule could be 0b00011110, or 30.
//
// To use that rule, we would mix together the previous states we’re
// interested in — left, middle, and right — which tells us which
// bit of the rule value gives our next state.
//
// The world size, rule and the number of generations, are read from stdin:
//
// $ ./cellular
// Enter world size: 60
// Enter rule: 30
// Enter how many generations: 10
//
// 0 …………………………#………………………..
// 1 ………………………..###……………………….
// 2 ……………………….##..#………………………
// 3 ………………………##.####……………………..
// 4 ……………………..##..#…#…………………….
// 5 …………………….##.####.###……………………
// 6 ……………………##..#….#..#…………………..
// 7 …………………..##.####..######………………….
// 8 ………………….##..#…###…..#…………………
// 9 …………………##.####.##..#…###………………..
// 10 ………………..##..#….#.####.##..#……………….
//
// $ ./cellular
// Enter world size: 60
// Enter rule: 30
// Enter how many generations: -10
//
// 10 ………………..##..#….#.####.##..#……………….
// 9 …………………##.####.##..#…###………………..
// 8 ………………….##..#…###…..#…………………
// 7 …………………..##.####..######………………….
// 6 ……………………##..#….#..#…………………..
// 5 …………………….##.####.###……………………
// 4 ……………………..##..#…#…………………….
// 3 ………………………##.####……………………..
// 2 ……………………….##..#………………………
// 1 ………………………..###……………………….
// 0 …………………………#………………………..
#include
#include
// maximum and minimum values for the 3 parameters
#define MIN_WORLD_SIZE 1
#define MAX_WORLD_SIZE 128
#define MIN_GENERATIONS -256
#define MAX_GENERATIONS 256
#define MIN_RULE 0
#define MAX_RULE 255
// characters used to print alive/dead cells
#define ALIVE_CHAR ‘#’
#define DEAD_CHAR ‘.’
// `cells’ is used to store successive generations. Each byte will be 1
// if the cell is alive in that generation, and 0 otherwise.
static int8_t cells[MAX_GENERATIONS + 1][MAX_WORLD_SIZE];
static void run_generation(int world_size, int which_generation, int rule);
static void print_generation(int world_size, int which_generation);
int main(int argc, char *argv[]) {
// read 3 integer parameters from stdin
printf(“Enter world size: “);
int world_size = 0;
scanf(“%d”, &world_size);
if (world_size < MIN_WORLD_SIZE || world_size > MAX_WORLD_SIZE) {
printf(“Invalid world size\n”);
return 1;
}
printf(“Enter rule: “);
int rule = 0;
scanf(“%d”, &rule);
if (rule < MIN_RULE || rule > MAX_RULE) {
printf(“Invalid rule\n”);
return 1;
}
printf(“Enter how many generations: “);
int n_generations = 0;
scanf(“%d”, &n_generations);
if (n_generations < MIN_GENERATIONS || n_generations > MAX_GENERATIONS) {
printf(“Invalid number of generations\n”);
return 1;
}
putchar(‘\n’);
// negative generations means show the generations in reverse
int reverse = 0;
if (n_generations < 0) {
reverse = 1;
n_generations = -n_generations;
}
// the first generation always has a only single cell which is alive
// this cell is in the middle of the world
cells[0][world_size / 2] = 1;
for (int g = 1; g <= n_generations; g++) {
run_generation(world_size, g, rule);
}
if (reverse) {
for (int g = n_generations; g >= 0; g–) {
print_generation(world_size, g);
}
} else {
for (int g = 0; g <= n_generations; g++) {
print_generation(world_size, g);
}
}
return 0;
}
//
// Calculate a new generation using rule and store it in cells
//
static void run_generation(int world_size, int which_generation, int rule) {
for (int x = 0; x < world_size; x++) {
// Get the values in the left and right neighbour cells.
// This requires some care, otherwise we could read beyond the
// bounds of the array. In the cases we are at the limits of
// the function, we consider those out-of-bounds cells zero.
int left = 0;
if (x > 0) {
left = cells[which_generation – 1][x – 1];
}
int centre = cells[which_generation – 1][x];
int right = 0;
if (x < world_size - 1) {
right = cells[which_generation - 1][x + 1];
}
// Convert the left, centre, and right states into one value.
int state = left << 2 | centre << 1 | right << 0;
// And check whether that bit is set or not in the rule.
// by testing the corresponding bit of the rule number.
int bit = 1 << state;
int set = rule & bit;
if (set) {
cells[which_generation][x] = 1;
} else {
cells[which_generation][x] = 0;
}
}
}
//
// Print out the specified generation
//
static void print_generation(int world_size, int which_generation) {
printf("%d", which_generation);
putchar('\t');
for (int x = 0; x < world_size; x++) {
if (cells[which_generation][x]) {
putchar(ALIVE_CHAR);
} else {
putchar(DEAD_CHAR);
}
}
putchar('\n');
}