Game of Probable Life
Authors: Emily, Philippe, and Phoebe
What is the problem?
We will use Haskell to implement a simulation of Conway's Game of Life.
Conway's Game of Life is a zero-player game that is played on a two-dimensional square grid of cells that are either alive or dead based on the following set of rules:
Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent
- Any live cell with fewer than two live neighbours dies, as if by underpopulation.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overpopulation.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
These rules, which compare the behaviour of the automaton to real life, can be condensed into the following:
- 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.
The initial pattern constitutes the seed of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed; births and deaths occur simultaneously, and the discrete moment at which this happens is sometimes called a tick. Each generation is a pure function of the preceding one. The rules continue to be applied repeatedly to create further generations.
(Adapted from Conway's Game of Life Wikipedia Page)
What is the something extra?
On top of implementing the Game of Life following Conway's rules, we will be taking a probabilistic approach to implementing the rules of the game. For each iteration of the game, rather than determining for certain whether each cell will be alive or dead based on their neighbours, each cell will instead have a chance of being alive or dead based on its neighbours. For example, in the probabilistic mode of our game, a dead cell with three living neighbours may only become alive 50% of the time, depending on the preferences set by the user.
Furthermore, we will be creating a GUI which will allow the user to interact with the rules of the game. This will display the game nicely in a different window and allow users to modify the probability as well as set the initial state of the board.
What did we learn from doing this?
Probability & IO
Through this project we learned how to make a line of code execute on a probabilistic basis using a random number generator. In order to accomplish this we did the following:
- If the probability was 70, we generated a list with 70 1's and 30 0's and picked an element at random index using the random generator
- If the number chosen was 1, the state of the cell would change, otherwise the cell's state remained the same
With using a random number generator, we also learned how to install and import packages into Haskell.
Because the random number generated an IO type, this forced many of our functions to return IO types which conflicted with a lot of the code we had written before. After doing some research and asking on piazza we managed to refactor our code so that the random number was generated elsewhere and passed on as a parameter instead of being directly called in all the functions. This restored the functionality in all our previous code.
From this project, we learned how to better deal with and properly utilize IO types as well as how to use pattern matching to extract from IO types.
GUI & gloss
In terms of the GUI, Gloss made the setup and development process smoother. However, we started by creating a game without using IO, using the package Graphics.Gloss.Interface.Pure.Game. We then realized we needed to switch the type of game to accommodate the IO variables returned by the functions in the backend. Therefore, we had to switch to using the package Graphics.Gloss.Interface.IO.Game to create a game that used IO. This resulted in some of refactoring of our program to support this.
We also how to better represent a world state in a data struct, and how to extract, add, and modify each element of that struct.