6
\$\begingroup\$

I have Conway's Game of Life working now and was hoping for direction on:

  1. Unclear code - where would comments make it clearer?
  2. Poor design choices - I already know of one in the countNeighbors method, as it needs knowledge of the game object's name
  3. 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()
\$\endgroup\$
  • 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 Dec 26 '14 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 Dec 27 '14 at 9:24
3
\$\begingroup\$

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)

\$\endgroup\$
  • \$\begingroup\$ I've added that - how can I utilise the functionality that this statement adds? \$\endgroup\$ – KBhas Dec 28 '14 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 Dec 28 '14 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 Dec 29 '14 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 Dec 29 '14 at 10:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.