You explain that you wish to improve CPU consumption but you
don't know where to begin.
Always start optimizing by asking "where did the cycles go?"
Run the profiler:
$ python -m cProfile -s tottime life.py
Also, a numba
add_life_to_matrix wouldn't hurt.
You are using list-of-lists, which is very pythonic,
but it involves lots of random reads for object pointer chasing.
Consider using a cache-friendly array
instead, or perhaps an
As with any project, definitely record timing measurements
before and after a refactor, so you're sure it had the
lg = LifeGame()
iteration = 0
iteration down into the game object.
You compute a very nice
coords data structure.
But it feels like that computation should
happen within the game class, perhaps as a local variable
I imagine there's a lot of flicker associated with that.
Rather than a heavyweight clearscreen approach,
you could just send an
Better, you could position the cursor at top-of-screen
and rewrite cells over displayed cells,
to reduce flicker.
Or consider using
pixels to represent live cells.
spawn_life you jump through a bunch of hoops
to create a temporary data structure and then
consume it. It's unclear why we'd want to create
it in the first place.
Just pick a random (x, y) and place a new
for i in range(1):
That's an odd way to say "do the following exactly once."
I assume it's leftover from debug, and that
1 is a
that you sometimes change.
Bury that constant in the signature instead:
def spawn_life(self, num_births=20):
for _ in range(num_births):
x, y = randint...
self.matrix[y][x] = "*"
No need for a return value that will just be ignored.
I tend to think of
get_available_coords as "get
coords", but whatever, that works fine.
available_coords = [[x-1, y], [x+1, y], [x, y+1], [x, y-1]]
Rather than list of lists, the pythonic way to phrase
that would be list of 2-tuples:
available_coords = [(x-1, y), (x+1, y), (x, y+1), (x, y-1)]
list for things that are all "the same",
tuple for things with fixed number of elements
where element position affects the meaning.
Think of it as an immutable C struct.
if is quite the mouthful. First let's use
to make it legible:
available_coords[i] <= self.width - 1
and available_coords[i] >= 0
and available_coords[i] <= self.height - 1
and available_coords[i] >= 0
and self.matrix[available_coords[i]][available_coords[i]] == " "
available_coords[i] expression is tediously long
and we use it several times, so let's assign temp vars:
x, y = available_coords[i]
Notice that element  went into x and  into y.
Now let's do some per-axis conjunct chaining:
0 <= x < self.width
and 0 <= y < self.height
and self.matrix[y][x] == " "
add_life_to_matrix the combination of these two
chance = random.randint(0, 100)
if chance == 1:
amounts to a Magic Number of
Better to put it in the method signature,
so unit tests or other callers can modify it when desired.
You asked about making this go faster,
presumably for a larger universe than 20 x 20 cells.
In 1970 Martin Gardner
Conway devised, and a rich literature has sprung up around it since.
It includes sightings of Gliders and of larger beasts, descriptions of how to find and construct such beasts,
and algorithms to efficiently compute the next generation
or the K-th subsequent generation.
Consider a canvas of more than 1000 x 1000 cells, with a
near the origin that keeps pumping out new gliders.
Out of a million cells, most will be dead in this
generation, and in every subsequent generation,
so they need not be recomputed.
Even if the original setup arranged for some isolated
to be present, they won't change either.
Segmenting the universe into interesting and unchanging
sections is key to efficiently computing large Life scenarios.
Typically we only refresh the display every K iterations, so
even an isolated blinker
or other cyclic beast may be ignored for a while, until we are
at last asked to render its neighborhood in the K-th generation.
This code achieves many of its design goals.
Some of the responsibilities currently shouldered
by callers could sensibly be pushed down into the game class.
I would be happy to delegate or accept maintenance tasks for this codebase.
There are some recommended code cleanups that really
ought to happen before merging to