Learning a New Programming Language, with Life (part 3)
I started learning Python three days ago, as described here in part 1 and part 2.
Just two hours a day, and I’m well on my way, because here below is a working implementation of my favorite, Conway’s Game of Life, in Python.
Today I refactored to put instance-specific initializations right in the functions that needed them, and promoted parsing of command line arguments to the main program. (Future: use argparse.)
I also realized that these functions are just procedures, so I stopped trying to return meaningful values. I removed the return statements. Python supports that, and returns None.
Update:
A day later, after questions from a reader led me to review my own code, I wondered why it works even without wraparound for negative indices!
The answer: Python lists handle negative indexes gracefully.
Let’s go to the code …
#Life.py
#Implementation of Conway’s Game of Life
#See https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
#Tom Harris 22-Jul-2015
#Tom Harris 23-Jul-2015 add multiple generations and display period to main program
#Import libraries needed
import sys #for command line argument support
#Define the Life class
class Life:
….#class attributes
….#None
….#class methods
….#NOTE: Currently all procedures — no return statement, implicitly returns None
….
….def readBoardFromFile(self, filename):
……..#Initialize (clear) the board of Life cells
……..self.mainBoard = []
……..#Open the file
……..initialGenerationFile = open(filename)
……..#Read from the file, line by line:
……..for line in initialGenerationFile:
…………listLine = list(line.rstrip(‘\r\n’))
…………self.mainBoard.append(listLine) #add the line, in Python list format, to the mainBoard
……..#Determine dimensions of the board after reading in
……..#WARNING: dimensions are 1 larger than largest index, since indices start at 0
……..self.maxRowMain = len(self.mainBoard)
……..self.maxColMain = len(self.mainBoard[0])
……..#Close the file
……..initialGenerationFile.close
….def displayBoard(self):
……..for rowIndex, row in enumerate(self.mainBoard):
…………for colIndex, col in enumerate(row):
…………….if self.mainBoard[rowIndex][colIndex]== “1”:
………………..print(“X”, end=”)
…………….else:
………………..print(“.”, end=”)
…………print() #print newline at end of each row
….def makeBoardCurrent(self): #TODO: Think of a better name — means to copy from the next-generation workingBoard to mainBoard
……..self.mainBoard = self.workingBoard
……..
….def calculateNextGeneration(self):
……..#Calculate next generation on workingBoard based on mainBoard
……..#Initialize work area
……..self.workingBoard = [] #A work area for creating the next Life generation
……..#Calculate next generation on workingBoard based on mainBoard
……..for mainRowIndex, mainRow in enumerate(self.mainBoard):
…………#Initialize working row before using for each row
…………workingRow = [] #for building up workingBoard row by row
…………for mainColIndex, mainElement in enumerate(mainRow):
…………….#Restart count of live cells
…………….numLiveCells = 0
…………….for i in [-1, 0, 1]:
………………..#Check row before, same row, next row
………………..rowOfCellToCheck = mainRowIndex + i
………………..#Wraparound
………………..if rowOfCellToCheck (self.maxRowMain – 1):
……………………rowOfCellToCheck = 0
……………………
………………..for j in [-1, 0, 1]:
……………………#Check column before, same column, next column
……………………colOfCellToCheck = mainColIndex + j
……………………#Wraparound
……………………if colOfCellToCheck (self.maxColMain – 1):
……………………….colOfCellToCheck = 0
……………………#If cell to check is alive, increment count of live cells
……………………#But if same row and column, ignore
……………………sameColAndRow = abs(i) + abs(j)
……………………if sameColAndRow != 0:
……………………….if self.mainBoard[rowOfCellToCheck][colOfCellToCheck] == “1”:
………………………. numLiveCells = numLiveCells + 1
…………………………………………
…………….if self.mainBoard[mainRowIndex][mainColIndex] == “1”: # The survival rules
………………..if 2 <= numLiveCells <= 3:
……………………workingRow.append(‘1’)
………………..else:
……………………workingRow.append(‘0’)
…………….else: #The reproduction rule
………………..if numLiveCells == 3:
……………………workingRow.append(‘1’)
………………..else:
……………………workingRow.append(‘0’)
……………………
…………self.workingBoard.append(workingRow)
…………………………..
#The actual main Life program
#Usage will be Life(filename, generations to calculate, every how many generations to display, display yes/no, write to files yes/no)
#First Usage Life() — done 22-Jul 10:24
#Second Usage Life(filename) — reading in file done 22-Jul-2015 10:59
#Third Usage Life(filename) — including displaying the file and dummy next generation 22-Jul-2015 18:34
#Fourth Usage Life(filename) — real next generation done 22-Jul-2015 20:17
#Fifth Usage Life(filename, generations to calculate, every how many generations to display) done 23-Jul-2015 20:44
#NOTE: Decided not to implement writing single generations to separate files. Seems unnecessary and would just fill my disk with lots of files.
#NOTE: Piping console output to text file is fine. Modern text editors can open large, multi-generation Life files and navigate quickly
#TODO: Next learning topics (backlog) would be:
#….1. Graphic display
#….2. Mouse input of boards
#….3. Change to argparse for robust command line parsing
#….4. Clean up file layout according to some accepted Python style guide
#….5. Refactor to be more functional as opposed to procedural, and reduce global variable use, if that makes code clearer
#Create a single Life instance
myLife = Life()
#Get the command line arguments
#TODO:Switch to Python argparse module instead
#From first command line argument, get pathname of initial (zero’th) generation text file
initialGenerationFilename = sys.argv[1]
#From second command line argument, get number of generations to calculate
numGenerations = int(sys.argv[2])
#From third command line argument, get period: every how many generations to display or write to file
#NOTE: currently, writing results to a file is not yet included
periodOfDisplay = int(sys.argv[3])
#Read in the initial (zero’th) generation board from text file
myLife.readBoardFromFile(initialGenerationFilename)
#Display initial (zero’th) generation
print(“Generation #”,0)
myLife.displayBoard()
print()
#Generate and display following generations
for generationIndex in range(1,numGenerations+1): #NOTE:Add 1 because range end is always one less than value
….myLife.calculateNextGeneration()
….myLife.makeBoardCurrent()
….if (generationIndex % periodOfDisplay) == 0: #NOTE: “%” is Python modulus — checking for evening divisible by period of display
……..print(“Generation #”,generationIndex)
……..myLife.displayBoard()
……..print()
And here’s some output. The 4-generation-period “glider” on a 25 x 25 grid, starts its flight from upper-left towards lower-right.
The command I ran was
Life.py Glider25by25.txt 8 4
and the output was
Generation # 0
..X......................
...X.....................
.XXX.....................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
Generation # 4
.........................
...X.....................
....X....................
..XXX....................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
Generation # 8
.........................
.........................
....X....................
.....X...................
...XXX...................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
.........................
[…] haven’t decided how soon to introduce the topic, but I thought I’d better check how my own sample program […]
Learning a New Programming Language, with Life (part 4) | Talk About Quality
September 2, 2015 at 10:31 pm