In my Computer Systems Organization class last year, one of our homework assignments was to code Conway’s Game of Life in C. The Game of Life is a cellular automaton game developed by a British mathematician named Conway.
The rules of the game (according to Wikipedia):
1. Any live cell with two or three live neighbours survives.
2. Any dead cell with three live neighbours becomes a live cell.
3. All other live cells die in the next generation. Similarly, all other dead cells stay dead.
The version we did in CSO was pretty lame simply using 1s and 0s printed, so I thought I could make a graphic version for my practice this week. I also lost access to the code I wrote, so I wanted to see if I could do it again and track whether it felt easier to build a year later as an indicator of my progression. The only thing I remembered from that assignment was creating classes for the Cell and Game.
My code was pretty straightforward. In general, I created the Cell and Game classes. I first began with coding the Cell class. Originally, I only had an attribute for the current state. However, when it came time to visually display it, I realized I needed a way to keep track of where to draw it. I ended up adding the x and y positions and size with each cell. The display of the tile occurred at the cell level with each cell being represented by a rectangle with its fill determined by whether it was currently alive or dead.
I first created the grid through a nested for loop setting up a cell at each row and column intersection. Then, for each generation of the cells, I looped through each cell and then counted how many of its neighbors were alive. Based on the rules of the game, I used its current state and number of live neighbors to determine its state in the next round. After each generation of the cells, I redrew the board.
The main struggles I had:
- Creating the 2D array. I kept getting an undefined length for the array. The problem? I didn’t actually define the dimension variable. Oops.
- Handling the edges of the grid. I ended up just checking to make sure the neighbor was within the bounds of the array, but I’m sure there’s a more efficient way to do this.
- Getting the right neighbor count. I realized I was double counting the cell itself if it was alive so ended up just subtracting it from the neighbor count.
I wanted to add some level of interaction and user input so ended up just making simple buttons with dimension size choices. I originally wanted to have a slider or a text input box so the user could have more choice in determining the size of the board, but these seemed quite laborious to implement in Processing. When clicking the button, a degree of user feedback is provided in the board changing sizes and the rectangle lighting up.
Here’s my sketch and code!
//Game of Life //Any live cell with two or three live neighbours survives. //Any dead cell with three live neighbours becomes a live cell. //All other live cells die in the next generation. Similarly, all other dead cells stay dead. //rules from https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life //the decision to create a Cell and Game class was based on the instructions given in my Computer Systems Organization course last year when we had to do this as a mini homework assignment in C //in that lab we simply used 1s and 0s so I wanted to graphically represent it. I also lost access to the code I wrote for that course so wanted to refresh my memory by retrying creating the game of life Game game; //array of grid dimension choices for the user int[] sizeChoice = new int[]{ 10, 25, 50, 100 }; //each cell has attributes of what its current state is //to graphically display the cell, i also include the x, y, and size in the class class Cell { boolean currAlive; int x; int y; int size; Cell(int x_pos, int y_pos, int size_) { //randomly decides if its alive or not by picking 0 or 1 randomly and casting that as a boolean currAlive = boolean(int(random(2))); x = x_pos; y = y_pos; size = size_; } void drawCell() { //if cell is alive, color it white if(currAlive) { fill(240); //if the cell is dead, make it black } else { fill(0); } //draw a tile to represent each cell rect(x,y, size, size); } } class Game { Cell[][] grid; int dimension; int size; Game(int dimension_) { //create a grid given what is passed to the constructor dimension = dimension_; grid = new Cell[dimension][dimension]; createCells(); } void createCells() { //make the size of tile responsive to the dimension size = width/dimension; //loop through each row and column to create a cell and draw the tile representing it for (int r = 0; r < dimension; r++) { for (int c = 0; c < dimension; c++) { grid[r][c] = new Cell(r*size, c *size, size); grid[r][c].drawCell(); } } } void generation() { //loop through each row and column in the grid to count neighbors for (int r = 0; r < dimension; r++) { for (int c = 0; c < dimension; c++) { //restart neighbor count for each cell int n_count = 0; //I kept getting the wrong cell count and realized that I forgot to exclude the cell itself n_count = n_count - int(grid[r][c].currAlive); //looping over its neighbors for (int i = -1; i < 2; i++) { for (int j = -1; j < 2; j++) { int x = r + i; int y = c + j; //found it difficult to check for the edge cells //ended up just checking to make sure the counting was within the array if(0 <= x && x < dimension && 0 <= y && y < dimension) { if(grid[x][y].currAlive) { n_count += 1; } } } } //for the rule if the cell has less than 2 or more than 3 neighbors, it dies in the next generation if (grid[r][c].currAlive && (n_count < 2 || n_count > 3)) { grid[r][c].currAlive = false; } //if the cell is dead but has 3 live neighbors, it becomes alive in the next generation if (!grid[r][c].currAlive && n_count == 3) { grid[r][c].currAlive = true; } } } } //redraw the grid after each generation void drawGrid() { for (int r = 0; r < dimension; r++) { for (int c = 0; c < dimension; c++) { grid[r][c].drawCell(); } } } } void setup() { size(500,600); //default grid size is 25 x 25 game = new Game(25); } void draw() { background(0); //creates buttons to change grid dimensions for (int n=0; n < sizeChoice.length; n++) { fill(123); int x_pos = width/8 * n + 20; rect(x_pos, height - 50, 30, 20); fill(255); text(sizeChoice[n], x_pos + 8, height-35); } text("Conway's Game of Life", width/3 * 2 - 20, height - 45); text("Change grid size with button", width/3 * 2 - 20, height - 25); //each frame is a new generation //redraw grid after every generation game.generation(); game.drawGrid(); } void mousePressed() { //checks if button is clicked if(mouseX < 50 && mouseX >= 20 && mouseY >= height - 50 && mouseY < height - 30) { pressButton(0); } if(mouseX < width/8 + 50 && mouseX >= width/8 + 20 && mouseY >= height - 50 && mouseY < height - 30) { pressButton(1); } if(mouseX < width/8 * 2 + 50 && mouseX >= width/8 * 2 + 20 && mouseY >= height - 50 && mouseY < height - 30) { pressButton(2); } if(mouseX < width/8 * 3 + 50 && mouseX >= width/8 * 3 + 20 && mouseY >= height - 50 && mouseY < height - 30) { pressButton(3); } } void pressButton(int n) { //changes fill if button is clicked and recreates the game with the new dimension fill(200); rect( width/8 * n + 20, height - 50, 30, 20); fill(255); text(sizeChoice[n], width/8 * n + 20 + 8, height-35); game = new Game(sizeChoice[n]); }