Two major suggestions:
First, let's try speeding everything up by taking advantage of the nature of Conway's Game of Life and the way C++ handles pointers. When you evaluate Conway's Game of Life, it's a two part process. First you copy all of the entries into a new array, then you do your computation referring to that new array, and update the original one. This process takes \$ O \left( n^{2} \right) \$, and involves a major memory copy; for small sizes that shouldn't be a problem, at larger sizes, the cost incurred by the memory copy will become non trivial, so let's reformulate the design a little:
Conway's Game of Life itself is an application of cellular automata, where the state G of the grid at time t is a function of of the state at a time t-1. More specifically, it would be of a form like this: G(t)=R(G(t-1)) where R() is some rule function determining which cells live and which cells die. This direct dependence offers us an interesting solution to the memory copying conundrum--what if we store two grids, one to represent G(t) and one to represent G(t-1)? Then we can get away with reading G(t-1), applying our rule and updating G(t). This however brings us back to the first question--how do we update G(t-1) then? do we just copy G(t)'s memory? C++ offers us a more convenient approach actually, we can swap the pointers instead: as G(t) is fully described by G(t-1) if we simply swap the pointers for the two arrays, so the pointer for G(t-1) refers to G(t)'s memory region, and G(t) refers to G(t-1)'s memory region, then we can apply the update rule, update G(t) to be G(t+1), overwriting the old memory used to store G(t-1), which prevents us from needing to copy the grids ever, which will significantly speed up the simulation in case n>500 in my experience.
The second comment is on code abstraction, cellular automata as a whole is a good place to explore this, because it's such a general field. It's not necessary to make your Conway's Game of Life code work, but it will make it easier if you come up with interesting variations you want to test. Right now you have the update rule, R() represented as a single function, liveOrDie, this can be re-factored into several smaller tasks however, which has the advantage of making the system more modular and easier to tweak. How then can we break down this rule into smaller, more modular components? Let us start by listing the actual procedure performed by the update function:
- Iterate over all of the cells and update them
- To update a cell, look up it's neighbors (in Conway's Game of Life we use the Moore neighborhood)
- Look at a cells neighbors and decide whether it will live or die (apply the rule to a cell)
- Update the contents of a cell according to it's rule
This list of tasks lends itself to defining three separate functions
void update(G(t),G(t-1)), which will perform the updating itself int neighbors(G,x,y), which will return the number of neighbors of a cell indexed at (x,y) bool rule(neighbors,lod,x,y) which will look at a specific cell and decide whether it lives or dies (lod is the state of the cell, live or dead)
The basic outline could then be something like this:
update(G(t),G(t-1)){ for(x: 1..size) for(y: 1..size){ G(t-1)[x+y*size] = rule(neighbors(G(t),x,y),G(t)[x+y*size],x,y) } swap = G(t-1) G(t-1) = G(t) G(t) = swap }
The advantage to this model is if you want to change the function of the rule, or the neighborhood, say apply periodic bounding, you can easily change only the definition related to that part of the code. Hope that helps.