Z OLD Tutorial 03 - james-bern/CS136 GitHub Wiki
Writeup
Prerequisite: Write straightforward code that π Does π The π Thing
On the last homework, you implemented code that simulates a 1D cellular automaton. If all went well, you ended up with "an extremely straightforward series of things happening."
Perhaps you had some comments like // initialize
, // update
, and // draw
.
Or, perhaps your code was so straightforward you that didn't feel any comments were necessary at all. π€·ββοΈ
(Or, perhaps your code was a bit of a mess. Uh oh π oh.)
Write the Usage Code first.
That code worked great, but now let's imagine we want to simulate multiple automata using Rule 22, one named Paul and one named Carl. Carl and Paul have different lengths, and we need to simulate them for a different number of generations. One way to solve this problem is by copy and pasting the code we wrote before.
// init carl
int[] carl = new int[79];
carl[carl.length / 2] = 1;
int[] carlWorkArray = new int[carl.length];
// init paul
int[] paul = new int[47];
paul[paul.length / 2] = 1;
int[] paulWorkArray = new int[paul.length];
// simulate carl
for (int generation = 0; generation < 16; ++generation) {
{ // print carl
String string = "";
for (int i = 0; i < carl.length; ++i) {
if (carl[i] == 0) {
string += ' ';
} else {
string += 'X';
}
}
System.out.println(string);
}
{ // update carl
// fill in carlWorkArray
for (int i = 1; i < carl.length - 1; ++i) {
int count = carl[i - 1] + carl[i] + carl[i + 1];
if (count == 1) {
carlWorkArray[i] = 1;
} else {
carlWorkArray[i] = 0;
}
}
// copy over carlWorkArray
for (int i = 0; i < carl.length; ++i) {
carl[i] = carlWorkArray[i];
}
}
}
// simulate paul
for (int generation = 0; generation < 24; ++generation) {
{ // print paul
String string = "";
for (int i = 0; i < paul.length; ++i) {
if (paul[i] == 0) {
string += ' ';
} else {
string += 'X';
}
}
System.out.println(string);
}
{ // update paul
// fill in paulWorkArray
for (int i = 1; i < paul.length - 1; ++i) {
int count = paul[i - 1] + paul[i] + paul[i + 1];
if (count == 1) {
paulWorkArray[i] = 1;
} else {
paulWorkArray[i] = 0;
}
}
// copy over paulWorkArray
for (int i = 0; i < carl.length; ++i) {
paul[i] = paulWorkArray[i];
}
}
}
This approach will work, but it's also "fragile." If we want to change how we print the automata, for example, we'll have to change the code in two different places. And, if we end up adding more automata, our code is going to get even bigger and repetitive.
Additionally, there's a rather terrifying potential for typos (and resulting hard-to-find bugs).
E.g., in the code above I wrote for (int i = 0; i < carl.length; ++i) paul[i] = paulWorkArray[i];
which is wrong bad very bad.
But it's hard to see typos like it in giant blobs of repeated code.
What shall we do?
The trick is to pull the repeated code out into functions (and, in Java, we'll typically structure those functions and the data they operate on into a class.)
I.e., let's imagine we have a cool Automaton1D
class that encapsulates the functionality we implemented on the last homework.
But don't start implementing the class just yet!
First, write some β¨Usage Codeβ¨, i.e., code that uses the class.
Make it code you would actually enjoy writing and working with.
Perhaps...something like this?
Automaton1D carl = new Automaton1D(79);
Automaton1D paul = new Automaton1D(47);
for (int generation = 0; generation < 16; ++generation) {
carl.print();
carl.update();
}
for (int generation = 0; generation < 24; ++generation) {
paul.print();
paul.update();
}
That seems pretty nice! And we still have control over some nitty gritty things like whether we print the automaton every generation.
Still, in certain cases maybe we want to wrap things up even further. Our usage code might then be something like this.
Automaton1D carl = new Automaton1D(79);
Automaton1D paul = new Automaton1D(47);
carl.simulate(16);
paul.simulate(24);
Skeleton the class
class Main {
public static void main(String[] arguments) {
Automaton1D carl = new Automaton1D(79);
Automaton1D paul = new Automaton1D(47);
carl.simulate(16);
paul.simulate(24);
}
}
class Automaton1D {
Automaton1D(int numCells) {
// TODO
}
void update() {
// TODO
}
void print() {
// TODO
}
void simulate(int numGenerations) {
// TODO
}
}
Implement the class (ideally, one method at a time) adding "state" as needed
class Main {
public static void main(String[] arguments) {
Automaton1D carl = new Automaton1D(79);
Automaton1D paul = new Automaton1D(47);
carl.simulate(16);
paul.simulate(24);
}
}
class Automaton1D {
int[] array;
int[] workArray;
Automaton1D(int numCells) {
this.array = new int[numCells];
this.array[this.array.length / 2] = 1;
this.workArray = new int[numCells];
}
void update() {
// fill in workArray
for (int i = 1; i < this.array.length - 1; ++i) {
int count = this.array[i - 1] + this.array[i] + this.array[i + 1];
if (count == 1) {
this.workArray[i] = 1;
} else {
this.workArray[i] = 0;
}
}
// copy over workArray
for (int i = 0; i < this.array.length; ++i) {
this.array[i] = this.workArray[i];
}
}
void print() {
String string = "";
for (int i = 0; i < this.array.length; ++i) {
if (this.array[i] == 0) {
string += ' ';
} else {
string += 'X';
}
}
System.out.println(string);
}
void simulate(int numGenerations) {
for (int generation = 0; generation < numGenerations; ++generation) {
this.print();
this.update();
}
}
}