In this video,
we are going to continue implementing Conway's Game of Life.
We are going to look at the source code in this video.
I thought it would only need one or two videos, but in fact, it has needed four.
So, apologies for that.
Let's start off by looking at live.h.
This has all the parameters for the simulation.
The characters for cell will be capital X, if the cell is alive, and a space if the cell is empty.
Which seems appropriate.
We are going to be displaying this on a standard ANSI compliant console.
This has rows, which go from 1 to 24, and columns, which go from 1 to 80.
We are also going to have a border of
cells which are permanently unpopulated, around this.
So that will correspond to row 0, column 0, row 24 and column 80.
So the maximum possible row number for a cell that is going to be displayed will be 23, and the maximum
possible column number will be 79.
Then we have the parameters
that Conway came up with. A live cell
needs two or three neighbours to survive, and an empty cell needs exactly three live neighbours to become
populated.
In the main() function,
we start off by telling the user what to expect, and what to do.
Then we wait for them to press the return key.
This is just the standard input.
It will stop and wait until the user presses the return key.
If they enter any other characters, then that will just be ignored.
I will come back to this bit in a minute!
Then we create a grid object, which will contain the first generation of cells.
We populate these cells at random.
And then we display this initial generation.
Then we wait for the user to press the return key, when they are ready to go on to the next iteration.
Then we create this object, which we use for calculating which cells will be populated in the next generation.
And then we copy all the cells, from the next generation over our current generation.
So we started off with the first generation.
Then we calculated which cells are alive in the second generation.
And then we copied all the cells over the current generation.
So the current generation now has the second generation of cells.
Then we go back to the start of the loop.
We display it.
Gets input.
Calculate the third generation.
Display that.
And keep going on and on.
And so on.
The cell class is really just a wrapper around a Boolean.
This will be true if the cell is alive, and false if it is empty.
We have a constructor which sets the status to false.
So by default, all the cells will be empty.
We can draw a cell at a particular position on the grid. And then we have some getter and setter functions.
So we can set the boolean to true, to make the cell alive.
We can set it to false, to make it empty.
And we can find out whether the cell is alive.
To draw the cell, we use one of these ANSI escape commands, the one which will move the cursor
to a given position.
These commands were designed by engineers,
so they start from one.
Unlike computer scientists, which have our array starting from zero.
So we need to add 1, to get the character displayed in the correct place.
So we send this escape code to the screen, which will move the cursor to the correct position, and
then we send the character that we want to display at that position.
The grid has an array of these cells.
We need to add 2 to the dimensions, to allow for the borders.
We can create a cell at a given position.
We can make it alive.
We can draw all the cells.
We can populate the cells at random.
And we have some functions for calculating what happens in the next generation.
This non-member function controls the process.
It takes the old generation and the new generation.
We have a member function which will determine whether a live cell will survive to the next generation,
a member function which determines whether an empty cell will become populated, and then a member function
which will update this grid object, with the data from the next generation.
The create() function just calls the create() function for the cell
in that position.
So this will make that cell to come to live.
If we are drawing all the cells, we need to clear the screen first.
So we send the ANSI escape command for that.
Then we iterate over all the grid's -
then we iterate over all the cells, including the ones in the border.
And then we draw each cell in the right place.
For populating the cells at random,
we iterate over all the cells in the visible grid, and then we do this calculation. And this will populate
the cell.
A factor of 5 means there is a one in 5 probability, so at 20% of the cells will be populated and
80% will be empty.
If we change this to 10, for example, then that will be a one in 10 probability.
So the initial grid would be much emptier.
I am not going to seed the random number generator here, because I want to get the same output every
time. Because I want to test and demonstrate.
And for that you need consistent output.
Obviously, if you were doing this for real, you would uncomment this and seed the random number generator,
so you would get different output every time you run it.
Then we have this function for determining whether a live cell will survive to the next generation.
First, we check that there is a live cell there.
If the cell is empty, there is nothing to survive.
So we return false.
Then we go through the neighbours of this cell. We call the is_alive() member function and add up the
results.
And that will give us the number of live neighbours for that cell.
If we have too many or too few, then the cell will not survive and we return false.
And if we survive to the end of the function, then the cell will survive to the next generation.
Then we have a very similar function for empty cells.
If there is a live cell, we return false.
We find the number of live neighbours.
And if we have the right number, we return true.
Otherwise false.
The update() function
just goes through all the visible cells in the grid, and it copies the value of the cell, from the
new generation, into the current generation.
And then we have this function, which oversees the calculation process.
It takes the old generation and the new generation.
It iterates over every cell in the grid, and then it determines whether the cell in the current grid
will survive.
And if so, it creates this in the new grid.
And similarly for empty cells.
Going back to the main function, there is some code which you need to uncomment, if you are using Visual
Studio or anything else that runs in Windows Console.
I will show you what happens first if you do not do that.
So there we are.
"Conway's Game of live."
"Press the return key to display each generation".
And then we get all this gibberish!
So the Windows Console has received all these ANSI escape commands.
But it does not know what to do with them, so it just displays them, without interpreting them.
So I needs to include this ansi_escapes.h,
which is this file.
So this declares two functions, which will put the console into a state where it processes ANSI commands.
And then, at the end, it will put it back to how it was initially.
And I think I found this code on Microsoft's website.
It is all low level Windows stuff.
All the code for this simulation is available as a downloadable resource.
So you can you compile yourself, look through it, and see for yourself how it works.
I am not a Windows expert, so I am not going to go into this.
So I need to include this file.
I also need to set up the console, before we display any output.
And then at the end, I need to put things back, how I found them.
So let's try this again.
Ah yes, that is much better.
So if we look at this, we have the border around the cells.
It is rather thin.
Then we have a cell at row 1, column 1.
This has just one live neighbour.
This cell here has three live neighbours.
This has one live neighbour.
This has three live neighbours.
And this has only two live neighbours. That one there is actually empty.
So in the next generation, we expect these two will have disappeared.
We have a cell there, and then two cells there. And nothing down here.
And there we are.
And then we keep on pressing return, to get through the generations.
And you can see structures starting to appear like this one.
So there are some stable structures.
For example, if you have a ring, every cell in the ring has exactly two live neighbours.
So these will keep each other alive.
So a structure like this could survive for generations, unless some other structure comes along and
disrupts it.
So, like that.
So you see, the ring has now been merged into a bigger structure.
And then we have another one down here.
This is another stable structure.
So what can you do to improve this?
Well, for example, you might want to leave this running for a long time to see what happens.
And having to sit there and keep pressing the return key might get a bit tedious. So you could just run it
in a flat-out loop, or you could put a sleep in, or a pause.
And the way to do that will depend on your operating system, so you can look that up.
Or alternatively, there is a way to do that in C++11, which I will cover later. So you can try that
out for yourselves if you like.
Another thing is, it is quite hard to keep track of which cells are appearing and disappearing between
iterations.
So you could have another grid, which you display in between the current generation and the next generation.
And on this in-between grid, you have the cells which are dying in red, maybe. The cells which are
being born in green, and the cells which survive in blue.
I have chosen a solution which is easy to implement, but it is not very efficient or accurate.
We have this border of empty cells around it, which stops cells wandering off the edge of the simulation.
But that is not actually correct.
If you look at the Wikipedia page for Conway's Game of Life, there are some ideas about how to make
this more extensible, without using too much memory or CPU time.
And there are also some variants on the Game of Life, variants on the Conway rules, and you could
look at those as well.
And of course, if you are into graphics or GUI programming, you can make this look a lot flashier.
Okay, so that is it for now.
I am doing control-C to escape!
I will see you next time.
But until then, keep coding!