I have Conway's Game of Life working now and was hoping for direction on:
- Unclear code - where would comments make it clearer?
- Poor design choices - I already know of one in the
countNeighbors
method, as it needs knowledge of the game object's name - Improvements to make - I have already noted that I wish to add more templates and the ability to rotate them. What would be a good idea to implement that would improve/demonstrate a coding concept?
#TODO - automate the generation cyling
#TODO - allow rotation of templates
#TODO - generate more templates (gun, oscillator,LWSS etc)
#TODO - don't require knowledge of the board object in the methods of the cell object!
from tkinter import *
import time
class game:
"""An object to store the game"""
def __init__(self, size):
self.size = size
self.cell_size = 25
window = Tk()
self.canvas = Canvas(window, width=size * self.cell_size, height=size * self.cell_size)
self.canvas.pack()
self.board = [[cell(i, j) for i in range(size)] for j in range(size)]
return None
def drawbox(self):
for j in range(self.size):
for i in range(self.size):
if self.board[i][j].alive:
self.box_colour = "#220C65"
else:
self.box_colour = "#B7F3D5"
self.canvas.create_rectangle(self.cell_size * i, self.cell_size * j,self.cell_size * i + self.cell_size,
self.cell_size * j + self.cell_size, fill=self.box_colour, outline="#FFFFFF",width=2)
return
def createGlider(self, x, y):
self.board[x - 1][y - 1].alive = True
self.board[x][y - 1].alive = True
self.board[x + 1][y - 1].alive = True
self.board[x + 1][y].alive = True
self.board[x][y + 1].alive = True
def createGun(self,x,y):
self.board
class cell:
"""An object for the cells in Conway's Game of Life"""
def __init__(self, y, x):
self.x = x
self.y = y
self.alive = False
#self.num_of_neighbors = 0
def countNeighbors(self):
self.num_of_neighbors = 0
for j in [-1, 0, 1]:
for i in [-1, 0, 1]:
#print("Testing coordinate: " + str(self.x+i)+","+str(self.y+j))
if i == 0 and j == 0:
#print("Not counting self")
continue
elif (self.x + i) < 0 or (self.y + j < 0):
#print("Avoiding negative indexing")
continue
try:
if my_game.board[self.x + i][self.y + j].alive:
#print("adding 1 - ")
self.num_of_neighbors += 1
continue
else:
#print("passing over a blank square")
continue
except IndexError:
#print("Index error caught - attempted to go out of bounds")
continue
def livingCellCheck(self):
if self.num_of_neighbors in [2,3]:
self.alive = True
else:
self.alive = False
def deadCellCheck(self):
if self.num_of_neighbors == 3:
self.alive = True
else:
self.alive = False
######################################################################################
num_of_generations = int(input("How many generations do you wish to simulate?\n"))
my_game = game(20)
my_game.drawbox()
my_game.board[1][1].alive = True
my_game.board[1][2].alive = True
my_game.board[2][2].alive = True
my_game.createGlider(5, 10)
my_game.drawbox()
for _ in range(num_of_generations):
for a in range(my_game.size):
for b in range(my_game.size):
my_game.board[a][b].countNeighbors()
#print(my_game.board[a][b].x, my_game.board[a][b].y)
#print("number of neighbors: " + str(my_game.board[a][b].num_of_neighbors))
#print(my_game.board[a][b].alive)
#print("*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*")
for a in range(my_game.size):
for b in range(my_game.size):
#print(my_game.board[a][b].num_of_neighbors)
if my_game.board[a][b].alive:
my_game.board[a][b].livingCellCheck()
else:
my_game.board[a][b].deadCellCheck()
input()
my_game.drawbox()
-
1\$\begingroup\$ Why do you insist on making it OO? OOP is not very well suited for this particular simulation and the program ends up being more complicated than it needs to. \$\endgroup\$Paul– Paul2014年12月26日 21:46:49 +00:00Commented Dec 26, 2014 at 21:46
-
\$\begingroup\$ I had it written simply using individual functions to manage each part of the process, but I wanted to have a bit of a play around with OO - partially to show that I understand the basics and can apply them. \$\endgroup\$KBhas– KBhas2014年12月27日 09:24:19 +00:00Commented Dec 27, 2014 at 9:24
1 Answer 1
In my opinion, I believe your code is pretty straight forward and doesn't need much extra comments (Perhaps to explain your steps in the 'countNeighbors' method).
Perhaps also consider to use
if __name__ == '__main__':
for the later part of your code (under the '####'),
which has two primary use cases:
Allow a module to provide functionality for import into other code while also providing useful semantics as a standalone script (a command line wrapper around the functionality)
Allow a module to define a suite of unit tests which are stored with (in the same file as) the code to be tested and which can be executed independently of the rest of the codebase.
(Source: https://stackoverflow.com/questions/22492162/understanding-the-main-method-of-python)
-
\$\begingroup\$ I've added that - how can I utilise the functionality that this statement adds? \$\endgroup\$KBhas– KBhas2014年12月28日 16:34:12 +00:00Commented Dec 28, 2014 at 16:34
-
\$\begingroup\$ You create a new function called def main(): in which you do what you already did, and at the bottom you add the line if name == 'main': where you call main(). This way your main() function will only be called when the python file itself is executed. Pretty easy, right? \$\endgroup\$DJanssens– DJanssens2014年12月28日 18:25:48 +00:00Commented Dec 28, 2014 at 18:25
-
\$\begingroup\$ Weren't the content of my main function already only being used when the python file was ran? Is this designed to give me more flexibility moving forward? i.e. I can design and write unit tests (or simply cases I want to visualise in this instance) and throw it under the if__name__=='main' part? Sorry for not following first time - this stuff is outside what I've learned so far! \$\endgroup\$KBhas– KBhas2014年12月29日 07:17:03 +00:00Commented Dec 29, 2014 at 7:17
-
\$\begingroup\$ When you want to reuse your code in the future. eg:if you extend your class 'game' with '2playergame' in a new file(module), you probably want to import this file. However by importing it will also perform the global written code, in this example (the part underneath '######', which you don't want, since you just want to import and reuse a part of its functionality. The f__name__=='main' part will only be executed if the file is directly executed. It makes your code more reusable. If you have more questions, feel free to ask me in chat chat.stackexchange.com/rooms/8595/the-2nd-monitor \$\endgroup\$DJanssens– DJanssens2014年12月29日 10:20:11 +00:00Commented Dec 29, 2014 at 10:20