10
\$\begingroup\$

I'm a Python novice and decided to give Game of Life a try in Python 3:

import os
import time
import curses
class Cell:
 newlabel = ''
 def __init__(self,row,col,label):
 self.row = row
 self.col = col
 self.label = label
 def updatecell(self,celllist, boardsize):
 liveneighbours = self.getliveneighbours(celllist, boardsize)
 if liveneighbours < 2 or liveneighbours > 3: #Kills cell if it has <2 or >3 neighbours
 self.newlabel = ' '
 elif liveneighbours == 2 and self.label == ' ': #Cell doesn't come alive if it is dead and only has 2 neighbours
 self.newlabel = ' '
 else: #Cell comes alive if it is already alive and has 2/3 neighbours or if it is dead and has 3 neighbours
 self.newlabel = 0
 def getliveneighbours(self,celllist,boardsize):
 count = 0
 for row in range(self.row-1,self.row+2):
 try:
 celllist[row]
 except: #Handles vertical wrapping. If there's an error, it checks the wrapped cell
 if row < 0:
 row += boardsize[0]
 else:
 row -= boardsize[0]
 for col in range(self.col-1,self.col+2):
 try: #Handles horizontal wrapping. If there's an error, it checks the wrapped cell
 celllist[row][col]
 except:
 if col < 0:
 col += boardsize[1]
 else:
 col -= boardsize[1]
 if not celllist[row][col].label:
 count += 1
 if not self.label: #Subtracts the cell from the neighbours count
 return count-1
 else:
 return count
 def updatelabel(self): #Updates the cell's label after all cell updates have been processes
 self.label = self.newlabel
class Board:
 celllist = {} #Dict of rows
 def __init__(self, rows, columns):
 self.rows = rows
 self.columns = columns
 self.generate()
 def printboard(self): #Prints the board to the terminal using curses
 for num, row in self.celllist.items():
 line = ''
 for col, cell in enumerate(row):
 line += str(cell.label)
 terminal.addstr(num, 0, line)
 terminal.refresh()
 def generate(self): #Adds all the cells to the board
 for row in range(self.rows-1):
 self.celllist[row] = []
 for col in range(self.columns):
 self.celllist[row].append(Cell(row,col,' '))
 def updateboard(self): #Prompts each cell to update and then update their label
 for row in self.celllist:
 for cell in self.celllist[row]:
 cell.updatecell(self.celllist, (self.rows-1, self.columns))
 for row in self.celllist:
 for cell in self.celllist[row]:
 cell.updatelabel()
if __name__ == "__main__":
 terminal = curses.initscr() # Opens a curses window
 curses.noecho()
 curses.cbreak()
 terminal.nodelay(1) #Don't wait for user input later
 rows, columns = os.popen('stty size', 'r').read().split()
 board = Board(int(rows), int(columns))
 board.celllist[6][8].label = 0
 board.celllist[7][9].label = 0
 board.celllist[7][10].label = 0
 board.celllist[8][8].label = 0
 board.celllist[8][9].label = 0
 while 1:
 board.printboard()
 time.sleep(0.1)
 board.updateboard()
 char = terminal.getch()
 if char == 113: #Checks for ASCII Char code for q and then breaks loop
 break
 curses.endwin() #Closes curses window

I've written it to work based on your terminal size and I've made the sides wrap around because it was the only solution I could think of. Also, at the end, I've included a glider example as a test.

Questions:

  1. Is my implementation pythonic enough (particularly concerning ranges and my constant use of iteration)?

  2. Are the data structures that I've used for the celllist (arrays in a dict) suitable?

  3. Is curses a good way of displaying the game? Would pyGame be better?

  4. Is the overall code style good?

  5. To further develop this, is there a better algorithm I could implement or a better method (other than wrapping the sides) of displaying all the cells?

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Apr 3, 2018 at 1:50
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$
  1. Running your code through pycodestyle will highlight some issues to make the code more familiar to Pythonistas. Other than that:
    • while 1 should be written as while True (Explicit is better than implicit)
    • Rather than checking for a specific character to quit I would just assume that people know about Ctrl-c
    • Use as easily readable names as possible. updatecell, for example, could be update_cell or even update since it's implicit that it's the Cell that's being updated.
    • Usually if __name__ == "__main__": is followed by simply main() or sys.exit(main()) (probably not relevant in your case). This makes the code more testable. Aim to minimize the amount of top-level code in general.
  2. The board is really a matrix, so it would IMO be better to represent it using a list of lists or ideally a matrix class. A dict has no intrinsic order, so semantically it's the wrong thing to use in any case.
  3. Somebody else will have to answer this.
  4. This is too subjective to answer.
  5. Some suggestions:
    • Try to take into account the fact that the Game of Life board is infinite in all directions. Ideally your implementation should handle this, but you could also use it as a stop condition - when things get too close to the edge to be able to figure out the next step with certainty the program could stop.
    • You can avoid conversions between numbers and strings by using numbers or even booleans everywhere (for example stored in a value or state field rather than label). You can then write a separate method to display a Board, converting values to whatever pair of characters you want, and possibly with decorations.
    • Rather than updating Cells individually by saving their new value you can replace the entire Board with a new one at each step.
    • The time.sleep() currently does not take into account that the Board update time might change. This is unlikely to be a problem for a small board, but if you want completely smooth frames you'll want to either use threads or a tight loop which checks whether it's time to print the current Board yet.
answered Apr 3, 2018 at 3:43
\$\endgroup\$
2
  • \$\begingroup\$ Thanks! How would I do this: "Rather than updating Cells individually by saving their new value you can replace the entire Board with a new one at each step." ? \$\endgroup\$ Commented Apr 3, 2018 at 5:02
  • 1
    \$\begingroup\$ You currently populate the newlabel field and then updatelabel once all the Cells are processed. You can avoid re-entering each Cell by creating the new Board based on the current Board, conceptually replacing newlabel with newboard. \$\endgroup\$ Commented Apr 3, 2018 at 6:18

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.