It's my implementation. Can I get some opinions on it?
import random
import os
import time
class Game:
def __init__(self, turns, w, h):
self.board = []
for x in range(0, h):
self.board.append([])
for y in range(0, w):
self.board[x].append(random.random())
self.turns = turns
def next_gen(self):
self.board_next = []
for x in range(0, len(self.board)):
self.board_next.append([])
for x in range(0, len(self.board)):
for y in range(0, len(self.board[x])):
n = 0
for dx in range(-1, 2):
for dy in range(-1, 2):
if (dx == 0 and dy == 0):
pass
else:
try:
if (x + dx >= 0 and y + dy >= 0):
if (self.board[x + dx][y + dy]):
n += 1
except IndexError:
pass
c = self.board[x][y]
if (n == 1 or n == 0):
c = 0
elif (n == 3):
c = 1
elif (n == 2):
pass
else:
c = 0
self.board_next[x].append(c)
self.board = self.board_next[:]
def show(self):
for x in range(0, len(self.board)):
l = ""
for y in range(0, len(self.board[x])):
if (self.board[x][y]):
l += "X"
else:
l += "#"
print(l)
time.sleep(0.5)
def start(self):
for x in range(0, self.turns):
print("\n+Gen {0}".format(x + 1))
self.show()
if (x + 1 != self.turns):
os.system('clear')
self.next_gen()
os.system('clear')
game = Game(3, 3, 3)
game.board = [
[0, 0, 0],
[1, 1, 1],
[0, 0, 0]]
game.start()
And output:
+Gen 1
###
XXX
###
+Gen 2
#X#
#X#
#X#
+Gen 3
###
XXX
###
1 Answer 1
First three general remarks:
range
does not need an explicit start argument, it defaults to 0.range
is basicallyrange(start=0, stop, step=1)
, onlystop
is neededif
takes an expression as argument which just has to evaluate truthy or falsey. There is no need for parenthesis, unless chaining multiple expressions over multiple lines or doing some complicatedand
/or
logic.- you should consistently use 4 spaces per indentation level (your class only has 2 in the outer level).
Your setting up of the board can be simplified with a list comprehension:
self.board = [[random.random() for _ in range(w)] for _ in range(h)]
Here I used _
as an unused loop variable, as customary in Python.
I would also save h
and w
in the class for later use.
In Game.next_gen
:
if (n == 1 or n == 0):
c = 0
elif (n == 3):
c = 1
elif (n == 2):
pass
else:
c = 0
is equivalent to:
if n == 3:
c = 1
elif n != 2:
c = 0
I would make two helper functions calculating neighbours that are alive:
def neighbours(self, x, y):
p = -1, 0, 1
for dx, dy in itertools.product(p, p)
if dx == dy == 0:
continue
_x, _y = x + dx, y + dy
if 0 <= _x < self.h and 0 <= _y < self.w:
yield _x, _y
def alive_neighbours(self, x, y):
return sum(self.board[n_x][n_y] > 0 for n_x, n_y in self.neighbours(x, y))
Here I used the boundaries to make sure that neighbours
only serves points inside the grid (and used itertools.product
to make it easier to construct dx
and dy
). Then getting the sum of all alive neighbours just sums over the values, since in self.board
empty cells have a value of zero, while alive cells a value greater than zero.
This simplifies next_gen
to:
def next_gen(self):
board_next = [[0 for _ in range(self.w)] * self.h]
for x in range(len(self.board)):
for y in range(len(self.board[x])):
n = self.alive_neighbours(x, y)
cell = self.board[x][y]
if n == 3:
cell = 1
elif n != 2:
cell = 0
board_next[x][y] = cell
self.board = board_next[:]
Your show
function can be simplified using str.join
:
def show(self):
for row in self.board:
print("".join("X" if cell else "#" for cell in row))
The time.sleep(0.5)
I would put in start
to make it possible to show the generation without delay if necessary.
I would put the code calling your class into a if __name__ == "__main__":
guard to allow importing your class from another module:
if __name__ == "__main__":
os.system('clear')
game = Game(3, 3, 3)
game.board = [
[0, 0, 0],
[1, 1, 1],
[0, 0, 0]]
game.start()