CS 467 - Assignment Five

Due: 2020-04-10 11:59p

Goals

  • Get familiar with a well known cellular automata
  • Experiment with other rule sets to get a sense of the commonalities as well as the differences

Prerequisites

  1. Accept the assignment on our Github Classroom.
  2. Download the git repository to your local computer.

Assignment

This assignment is a little more straightforward than the last one, though you will still have the opportunity for creative expression.

I would like you to implement at least three different cellular automata. One of these will be the Game of Life, for which the reading provides an implementation (though, as in the practical, the implementation will be a little different).

For the other two, at least one must be an entirely new CA. The third can be a variant (e.g., it could use age to determine coloring, or use a different set of parameters) or it could be another unique rule set. The only real requirement is that all three look substantially different. You are also not limited to three either.

For all of your CAs, I would like you to default to wrapping the edges. If there is a compelling reason not to (the wave CA, for example, will have different behavior depending what you pick), pick what makes aesthetic sense to you and leave a comment in the code the explain why the choice was made.

Starter code

To get you started, I have provided somewhat more template code than I have for earlier assignments. I am also getting you to break outside of the single .js file model that we have been using.

You will see that there are two .js files in the template code: driver.js and gol.js.

driver.js

This file holds the setup() and draw() functions and should be the first one that is added in index.html. There are a couple of important features to look for in the code.

I have given you a variable called cells, which will hold the 2D array representing your CA. You should also notice that I implemented the draw() method for you as well, which handles the update and the display of the CA cells.

I've added in a select menu that the user will use to select different CAs.

The subtler piece in this file is the CA_COLLECTION. This is a map that you will use to manage all of your CAs. The keys of the map are the name that you want to show up in the select menu. The associated values will be the function to call to generate the cells. Consider these lines in the code:

const name = options.value();
const generator = CA_COLLECTION.get(name);
cells = generator(CELL_SIZE);

This gets the name from the select menu. Then it gets the generator function from the map. The generator is called with the cell size and then returns a 2D array of cells, which is stored in cells.

For the most part, you can leave this file completely alone. You may want to adjust the cell size, but unless you want to do anything fancy, there should be no reason to touch this file.

To give you an example of how you write this generator and add it to the map, I provided you with a shell for the Game of Life CA.

gol.js

Use this file as a template for all of your CAs.

At the highest level, there are only two things in this file, a function called gameOfLife, which is the actual generator, and a line at the bottom which registers this function with the map.

The function is fairly straightforward. Inside, you will see that it has a familiar looking Cell class (the implementation of which is up to you and the specifics of the CA). There is then some code that generates a 2D array of these Cell objects and returns them.

This is just one of many ways that we could implement this. The advantage of this approach is that the Cell class is hidden inside of the closure of the function and thus exists in a local namespace. All of your CA implementations can use a different Cell class, and there won't be a conflict.

Looking at the last line of the file (CA_COLLECTION.set('Game of Life', gameOfLife);), we can see that this is loading the function into the map keyed to the name 'Game of Life'.

Making variants

A number of CAs have parameters which govern their behavior, such as the number of possible states. The Hodgepodge (see below) for example, has four.

If you are only interested in one set of parameters, then you can just hard code them into the generator function. But what if you want multiple variations to be options for the user?

You could just copy the file, rename the function and hard code in a different set of parameters (don't do that).

A better solution is to add those parameters you want control over as parameters of the generator. However, the code in driver.js won't know anything about these extra parameters. You can solve that by making use of JavaScript's anonymous functions and creating a partial function application. A partial function application is one where we lock in the values of a function to create a new function with fewer arguments.

Here is an example. Let's say you implement a new CA called "Wicked Cool CA". Depending on the number of states, it has very different behavior, and you are particularly taken with its behavior when there are 100 states and 42 states. So, you write the generator and give it two arguments: wickedCool(cellSize, numStates).

To add the two variants to the map, you could do something like this:

CA_COLLECTION.set('Wicked Cool 42', (cellSize)=>wickedCool(cellSize, 42));
CA_COLLECTION.set('Wicked Cool 100', (cellSize)=>wickedCool(cellSize, 100));

Notice how we used an anonymous function that took one argument (what our driver is expecting) and used it to wrap a call to our actual generator, fixing the second argument to the value we wanted.

Summary

So, to add a new CA to the collection:

  • Create a new .js file modeled on gol.js
  • Add a script tag to `index.html to load the new file
  • In the file, create a generator function that returns a 2D array of cells
  • At the bottom of the file, create at least one entry to the map, registering your generator with the name to appear in the select menu

CAs

This is a short list of possible CAs to implement. You are welcome to poke around and find other ones or try to invent your own.

Variants

Conway's Game of Life and the other binary CAs are pretty straightforward. There are a number of things that you could do to make the rendering more interesting. One technique is to color based on both the state and the next state, so you can see cells that are about to transition.

Another variant is to add an age variable that keeps track of how long the cell has been in a particular state and colors the cell based on the state and the age.

If you want to go wilder, there is no obligation to actually draw a little rectangle and use the state for coloring. You could draw other shapes that grow and shrink and change color with respect to the state and position. Go nuts.

Cyclic CA

This is a CA based on modulo arithmetic. Cell state is a number in the range 0-N, where N is determined at the start. In update, the cell examines its neighborhood. If the state of any of the neighbors is exactly one more (modulo N) than the cells' state, then the next state of the cell matches it. The effect of this feels a lot like joining the in crowd by latching on to someone just a little bit older than you. This one starts slow and then pools of conformity start to form, wiping out most of the non-conformists as the cycling numbers sweep by then and suck them in.

Vichniac Vote

This is another binary CA, and it is a more direct modeling of peer group pressure. At update, the cell tallies up how many members of its neighborhood (including itself) are in each state. If it is part of the minority, it changes to join the majority, otherwise it stays the same. (This one is another candidate for modifications like coloring by age).

Hodgepodge

The Hodgepodge is a curious CA that is modeled on the way some bacteria behaves (something called the Belousov Zhabotinsky reaction). It is really a model of spreading infection, which feels very topical. Initially, it behaves a bit like the cyclic CA, but under some circumstances, it starts to form some really great spirals.

Again, cell state is a number from 0-N, where N is determined at the beginning, though unlike the cyclic CA, N is a valid state, so there are a total of N+1 possible states. Cells in state 0 are considered to be "healthy", a state greater than 0 and less than N is considered "infected" (with larger values equating to more elevated levels of infection), and state N is considered "sick".

In addition to N, there are three other constants that govern the behavior of the CA.

  • k1 controls the effect of infected cells
  • k2 controls the impact of ill cells
  • g controls the overall rate infections worsen

Here are the rules:

The cell is healthy Visit all of the neighbors and count the number of ill and the number of infected neighbors. The next state of the cell is infectedk1+illk2\left \lfloor{\frac{\text{infected}}{k1}} \right \rfloor + \left \lfloor{\frac{\text{ill}}{k2}} \right \rfloor.

The cell is ill If the state of the cell is N, then set its next state to 0. In other words, it gets better.

The cell is infected Visit all of the neighbors and count the number of ill and the number of infected neighbors. At the same time, the states of all of the neighbors and the cell itself (let's call this value sum). The next state is sumill+infected+1+g\left \lfloor{\frac{\text{sum}}{\text{ill} + \text{infected} + 1}} \right \rfloor + g.

I found a nice blog post about this CA that includes both pictures of the actual reaction as well as a collection of example outputs of the CA with values for the constants. He has written a number of posts about other CAs, so this could be a good source of ideas.

Waves

This one is based on averaging. There are 256 states (0-255, corresponding to the 256 levels of grey). This one also adds an additional wrinkle in that it requires the cell to not just know its current state and next state, but also its previous state.

The first step of the update is to compute the average state of the neighborhood (not including the cell itself). Take the floor to make it an integer.

  • If the average is 255, the state becomes 0
  • If the average is 0, the state becomes 255
  • Otherwise, the next state is equal to the current state + the average of the neighborhood - the previous state (with the value constrained to remain between 0 and 255)

This one is more sensitive to initial conditions and will require some tweaking. If you leave everything at 0, it will just strobe back and forth.

Requirements

  • The 'Game of Life' option must display a straight up implementation of the Game of Life without additions or modifications.
  • A second unique set of rules must be included as an option
  • A third option must be present, it can be either a third unique CA, or it can be a variant of one of the other two
  • The user must be able to smoothly flip back and forth between options without issues
  • Sophisticated Add a control to change the cell size at run time
  • Sophisticated Allow the user to "paint" in values with the mouse (this is straightforward for binary CAs, slightly less so for others)
  • Sophisticated Add a fourth CA or variant
  • Sophisticated Add a control to switch the grid lines off and one
  • Sophisticated Add a user control that controls the display frequency

*A Sophisticated mark will be achieved by meeting all Sophisticated requirements*

You are, of course, welcome to innovate beyond these requirements if inspiration strikes.

Display frequency and speed

You probably are wondering what is meant by "display frequency". Running CAs is compute intensive. As the cell size drops, and the number of cells increases, you will find that the refresh speed drops to a crawl. One technique to address this is to not draw every state change. You could, for example, draw every other update. Or every four updates, or every ten. I won't pretend that this suddenly makes your code speed along (spoiler: it won't). If anything it will seem slower, but the state of the system will advance quicker. For a system like the hodgepodge, this makes it easier to get to the interesting stages.

Go above and beyond in search of speed

If you are feeling like you don't have enough to do and this stuff is coming very easy, there is an alternative way to earn a Sophisticated mark. In fact, I will will consider this an opportunity to earn a second Sophisticated mark to be applied to an assignment of your choice. Do not take this on lightly however...

Computing next state values is embarrassingly parallel. One of the best tools for performing highly parallel tasks is a graphics card. There are a variety of ways to perform computation on a graphics card, but one way that we have access to through p5.js is via shaders. These are short programs in another language (in this case, GLSL). If the current state were encoded as a texture, it could be passed down to the shader, and the next state could be calculated for the cells in parallel. If you have never seen a shader before, this would take quite a bit of research, but it is not beyond some of you (if you are still a little shaky on your JavaScript, give this one a miss).

Finishing up

Commit your changes to git and push them back up to GitHub. I will find them there.