PJI Homepage 6
Picture of me.
#! /usr/bin/python
# grid: A library of objects built for an implementation of Conway's Game of 
# Life.
# Paul J. Iutzi
# 4.11.2008

class Grid(object):
	'''Grid: Creates a matrix Grid.grid that is Grid.ranks wide and Grid.rows 
	tall. Defaults are optimized for display in a 80x24 terminal.'''
	grid = []
	rows = 19
	ranks = 78
	
	def __init__(self):
		self.grid = []
		for row in range(self.rows):
			self.grid.append([0] * self.ranks)
			
	def clear(self):
		'''clear: An alias of __init__(), which clears the grid.'''
		self.__init__()
	
	def show(self):
		'''show: A simple method for displaying the grid on stdout.'''
		print "-" * (self.ranks + 2)
		for row in range(self.rows):
			catRow = "|"
			for rank in range(self.ranks):
				catRow = catRow + self.grid[row][rank]
			catRow = catRow + '|'
			print catRow
		print "-" * (self.ranks + 2)
		
	def save(self, path):
		'''save: Save the grid and its dimensions to a txt file.'''
		savefile = open(path, 'w')
		savefile.write(str(self.rows) + 'x' + str(self.ranks) + '\n')
		for row in range(self.rows):
			catRow = ''
			for rank in range(self.ranks):
				catRow = catRow + str(self.grid[row][rank])
			catRow = catRow + "\n"
			savefile.write(catRow)
		savefile.close()
			
	def load(self, path):
		'''load: Load the grid and its dimensions from a txt file saved from 
		Grid.save.'''
		result = []
		loadfile = open(path, 'r')
		gridDim = loadfile.readline().split('x')
		self.rows, self.ranks = int(gridDim[0]), int(gridDim[1])
		self.clear()
		for row in range(self.rows):
			result.append(loadfile.readline())
		loadfile.close()
		self.update(result)
		
	def update(self, changes):
		'''update: Given changes, change the contents of the grid. Changes 
		must be an array of rows number of strings ranks long.'''
		for row in range(self.rows):
			try:
				s = changes[row]
				for rank in range(self.ranks):
					try:
						self.grid[row][rank] = int(s[rank])
					except IndexError:
						self.grid[row][rank] = 0
					except ValueError:
						self.grid[row][rank] = 1
			except IndexError:
				self.grid[row] = [0] * self.ranks
		
	
	def getSurrounds(self, x, y, hRange=1, vRange=1):
		'''getSurrounds: Returns a list of the fields surrounding a field in 
		the grid. The radius is determined by hRange and vRange.'''
		surrounds = []
		for row in range((y - vRange), (y + vRange + 1)):
			for rank in range((x - hRange), (x + hRange + 1)):
				surrounds.append([(rank % self.ranks), (row % self.rows)])
		surrounds.pop(surrounds.index([x, y]))
		return surrounds


class LifeGrid(Grid):
	'''LifeGrid: Creates a grid specialized to run Conway\'s Game of Life.'''
	survive = 2
	birth = 3
	crowded = 4
	live = 1
	dead = 0
	
	def show(self, fieldOn='*', fieldOff=' '):
		'''show: A simple method for displaying the grid to stdout, which is 
		specialized for GoL. It allows the customization of what is printed 
		when a field is on or off when the method is invoked.'''
		print "-" * (self.ranks + 2)
		for row in range(self.rows):
			catRow = "|"
			for rank in range(self.ranks):
				if self.grid[row][rank] == self.live:
					catRow = catRow + fieldOn
				else:
					catRow = catRow + fieldOff
			catRow = catRow + '|'
			print catRow
		print "-" * (self.ranks + 2)

	def bare(self, x, y):
		'''bare: Turns on a field in the grid.'''
		self.grid[y][x] = self.live
	
	def kill(self, x, y):
		'''kill: Turns off a field in the grid.'''
		self.grid[y][x] = self.dead
	
	def lifeCheck(self, x, y):
		'''lifeCheck: Returns whether a field on the grid is on or off.'''
		if self.grid[y][x] == self.live:
			return self.grid[y][x]
		else:
			return []
	
	def surroundCheck(self, x, y):
		'''surroundCheck: Given a field on the grid. Returns the number of 
		\"on\" fields surrounding that field.'''
		lifeCount = 0
		surrounds = self.getSurrounds(x, y)
		for i in surrounds:
			if self.lifeCheck(i[0], i[1]):
				lifeCount += 1				
		return lifeCount
	
	def spreadCheck(self, lifeCount, alive):
		'''spreadCheck: Given the count of \"on\" fields surrounding a field 
		on the grid and whether the field is on. Returns 0 if the field 
		should be turned off and 1 if the field should be turned on.'''
		if lifeCount < self.survive:
			return 0
		elif lifeCount < self.birth:
			if alive:
				return 1
			else:
				return 0
		elif lifeCount < self.crowded:
			return 1
		else:
			return 0
	
	def round(self):
		'''round: Advances the GoL by 1 generation.'''
		birthlist = []
		deathlist = []
		for row in range(self.rows):
			for rank in range(self.ranks):
				spread = self.spreadCheck(self.surroundCheck(rank, row), self.lifeCheck(rank, row))
				if spread:
					birthlist.append([rank, row])
				else:
					deathlist.append([rank, row])
		for birth in birthlist:
			self.bare(birth[0], birth[1])
		for death in deathlist:
			self.kill(death[0], death[1])
			
	def showRounds(self, rounds):
		'''showRounds: Loops through several generations of the GoL.'''
		for i in range(rounds):
			print "Round " + str(i + 1)
			self.round()
			self.show()
			
	def random(self):
		'''random: Randomly turns fields on and off in the grid.'''
		import random
		for row in range(self.rows):
			for rank in range(self.ranks):
				if random.random() < .5:
					self.kill(rank, row)
				else:
					self.bare(rank, row)
				
	def makeBlinker(self, x, y):
		'''makeBlinker: Given x and y, creates a blinker on the grid where x 
		and y are the coords for the upper left corner.'''
		self.bare(x % self.ranks, y % self.rows)
		self.bare((x+1) % self.ranks, y % self.rows)
		self.bare((x+2) % self.ranks, y % self.rows)

	def makeBlock(self, x, y):
		'''makeBlock: Given x and y, creates a block on the grid where x 
		and y are the coords for the upper left corner.'''
		self.bare(x % self.ranks, y % self.rows)
		self.bare((x+1) % self.ranks, y % self.rows)
		self.bare(x % self.ranks, (y+1) % self.rows)
		self.bare((x+1) % self.ranks, (y+1) % self.rows)

	def makeGlider(self, x, y):
		'''makeGlider: Given x and y, creates a glider on the grid where x 
		and y are the coords for the upper left corner.'''
		self.bare(x % self.ranks, y % self.rows)
		self.bare((x+1) % self.ranks, y % self.rows)
		self.bare((x+2) % self.ranks, y % self.rows)
		self.bare(x % self.ranks, (y+1) % self.rows)
		self.bare((x+1) % self.ranks, (y+2) % self.rows)

	def makePiston(self, x, y):
		'''makePiston: Given x and y, creates a piston on the grid where x 
		and y are the coords for the upper left corner.'''
		self.bare(x % self.ranks, y % self.rows)
		self.bare((x+1) % self.ranks, (y) % self.rows)
		self.bare((x+9) % self.ranks, (y) % self.rows)
		self.bare((x+10) % self.ranks, (y) % self.rows)
		self.bare((x) % self.ranks, (y+1) % self.rows)
		self.bare((x+2) % self.ranks, (y+1) % self.rows)
		self.bare((x+5) % self.ranks, (y+1) % self.rows)
		self.bare((x+8) % self.ranks, (y+1) % self.rows)
		self.bare((x+10) % self.ranks, (y+1) % self.rows)
		self.bare((x+2) % self.ranks, (y+2) % self.rows)
		self.bare((x+3) % self.ranks, (y+2) % self.rows)
		self.bare((x+4) % self.ranks, (y+2) % self.rows)
		self.bare((x+5) % self.ranks, (y+2) % self.rows)
		self.bare((x+8) % self.ranks, (y+2) % self.rows)
		self.bare((x) % self.ranks, (y+3) % self.rows)
		self.bare((x+2) % self.ranks, (y+3) % self.rows)
		self.bare((x+5) % self.ranks, (y+3) % self.rows)
		self.bare((x+8) % self.ranks, (y+3) % self.rows)
		self.bare((x+10) % self.ranks, (y+3) % self.rows)
		self.bare((x) % self.ranks, (y+4) % self.rows)
		self.bare((x+1) % self.ranks, (y+4) % self.rows)
		self.bare((x+9) % self.ranks, (y+4) % self.rows)
		self.bare((x+10) % self.ranks, (y+4) % self.rows)


class LifeController(object):
	'''LifeController: Simple terminal UI for LifeGrid.'''
	g = LifeGrid()
	
	def __init__(self):
		self.g.load("default")
		self.roundsLoop()

	def show(self, fieldOn='*', fieldOff=' '):
		'''show: A simple method for displaying the grid to stdout, which is 
		specialized for GoL. It allows the customization of what is printed 
		when a field is on or off when the method is invoked.'''
		print "-" * (self.g.ranks + 2)
		for row in range(self.g.rows):
			catRow = "|"
			for rank in range(self.g.ranks):
				if self.g.grid[row][rank] == self.g.live:
					catRow = catRow + fieldOn
				else:
					catRow = catRow + fieldOff
			catRow = catRow + '|'
			print catRow
		print "-" * (self.g.ranks + 2)

	def roundsLoop(self):
		'''roundsLoops: Creates a simple UI for the GoL.'''
		self.show()
		while 1:
			print "GoL: (Q)uit, (C)lear, (A)dd, (D)elete, (R)andom, (S)ave, (L)oad, (N)ext Gen"
			cmd = raw_input('> ')
			if (cmd == 'q') | (cmd == 'Q'):
				raise SystemExit
			elif (cmd == 'c') | (cmd == 'C'):
				self.g.clear()
				self.show()
			elif (cmd == 'r') | (cmd == 'R'):
				self.g.random()
				self.show()
			elif (cmd == 'a') | (cmd == 'A'):
				self.uiAdd()
				self.show()
			elif (cmd == 'd') | (cmd == 'D'):
				self.uiDelete()
				self.show()
			elif (cmd == 'n') | (cmd == 'N') | (cmd == ''):
				self.g.round()
				self.show()
			elif (cmd == 's') | (cmd == 'S'):
				self.uiSave()
				self.show()
			elif (cmd == 'l') | (cmd == 'L'):
				self.uiLoad()
				self.show()
			else:
				print "Error: " + cmd + " not a valid command."
			
	def uiSave(self):
		'''uiSave: Creates a simple UI for saving files.'''
		self.show()
		print "Save Path:"
		path = raw_input('> ')
		self.g.save(path)

	def uiLoad(self):
		'''uiLoad: Creates a simple UI for loading files.'''
		self.show()
		print "Load Path:"
		path = raw_input('> ')
		self.g.load(path)
	
	def uiAdd(self):
		'''uiAdd: Creates a simple UI loop for turning fields on.'''
		self.show()
		while 1:
			print "Add Life: (Q)uit, (x,y)"
			cmd = raw_input('> ')
			if (cmd == 'q') | (cmd == 'Q') | (cmd == ''):
				break
			elif cmd.find(',') != -1:
				coords = cmd.split(',')
				if len(coords) != 2:
					print "Error: Need exactly 2 coordinates."
					break
				else:
					self.g.bare(int(coords[0]), int(coords[1]))
					self.show()
			else:
				print "Error: " + cmd + " not a valid command."
					
	def uiDelete(self):
		'''uiDelete: Creates a simple UI loop for turning fields off.'''
		self.show()
		while 1:
			print "Delete Life: (Q)uit, (x,y)"
			cmd = raw_input('> ')
			if (cmd == 'q') | (cmd == 'Q') | (cmd == ''):
				break
			elif cmd.find(',') != -1:
				coords = cmd.split(',')
				if len(coords) != 2:
					print "Error: Need exactly 2 coordinates."
					break
				else:
					self.g.kill(int(coords[0]), int(coords[1]))
					self.show()
			else:
				print "Error: " + cmd + " not a valid command."


class CLC(object):
	'''CLC: A simple terminal UI for GoL using curses with wrapper.'''
	import curses
	g = LifeGrid()

	def __init__(self):
		self.g.load("default")
		self.curses.wrapper(self.roundsLoop)

	def show(self, stdscr, prompt='', fieldOn='*', fieldOff=' '):
		'''show: A simple method for displaying the grid to stdout, which is 
		specialized for GoL. It allows the customization of what is printed 
		when a field is on or off when the method is invoked.'''
		#print "-" * (self.g.ranks + 2)
		for row in range(self.g.rows):
			stdscr.addstr(row, 0, '|')
			for rank in range(self.g.ranks - 1):
				if self.g.grid[row][rank + 1] == self.g.live:
					stdscr.addstr(row, rank + 1, fieldOn)
				else:
					stdscr.addstr(row, rank + 1, fieldOff)
			stdscr.addstr(row, self.g.ranks + 1, '|')
			#print catRow
		#print "-" * (self.g.ranks + 2)		
		stdscr.move(23, 0)
		stdscr.deleteln()
		stdscr.addstr(prompt)

	def roundsLoop(self, stdscr):
		'''roundsLoops: Creates a simple UI for the GoL.'''
		prompt = "GoL: (Q)uit, (N)ext, (S)ave, (L)oad, (E)dit, (R)andom, (C)lear, (A)uto"
		autoFlag = False
		#stdscr.nodelay(True)
		while True:
			self.show(stdscr, prompt)
			cmd = stdscr.getch()
			if cmd != -1:
				if (cmd == ord('q')) | (cmd == ord('Q')):
					self.doQuit()
				elif (cmd == ord('n')) | (cmd == ord('N')):
					self.g.round()
				elif (cmd == ord('s')) | (cmd == ord('S')):
					self.uiSave(stdscr)
				elif (cmd == ord('l')) | (cmd == ord('L')):
					self.uiLoad(stdscr)
				elif (cmd == ord('e')) | (cmd == ord('E')):
					self.uiEdit(stdscr)
				elif (cmd == ord('r')) | (cmd == ord('R')):
					self.g.random()
				elif (cmd == ord('c')) | (cmd == ord('C')):
					self.g.clear()
				elif (cmd == ord('a')) | (cmd == ord('A')):
					if autoFlag == 0:
						autoFlag = 1
						stdscr.nodelay(True)
					else:
						autoFlag = 0
						stdscr.nodelay(False)
			else:
				self.g.round()

	def doQuit(self):
		'''doQuit: Resets the terminal upon quitting.'''
		raise SystemExit
		
	def uiSave(self, stdscr):
		'''uiSave: Creates a simple UI for saving files.'''
		prompt = "Save Path: "
		self.show(stdscr, prompt)
		self.curses.nocbreak()
		self.curses.echo()
		path = stdscr.getstr()
		self.g.save(path)
		self.curses.cbreak()
		self.curses.noecho()

	def uiLoad(self, stdscr):
		'''uiLoad: Creates a simple UI for loading files.'''
		prompt = "Load Path: "
		self.show(stdscr, prompt)
		self.curses.nocbreak()
		self.curses.echo()
		path = stdscr.getstr()
		self.g.load(path)
		self.curses.cbreak()
		self.curses.noecho()
		
	def uiEdit (self, stdscr):
		'''uiEdit: Creates a simple UI for editing the GoL board.'''
		import curses.textpad
		prompt = "Edit: (*)Add, ( )Delete, (^G)Exit"
		self.show(stdscr, prompt)
		editscr = curses.textpad.Textbox(stdscr)
		changes = editscr.edit()
		changeList = changes.split('\n')
		self.g.update(self.parseEdit(changeList))
		
	def parseEdit(self, changes):
		result = []
		for line in changes:
			line = line[1:-1]
			line = line.replace('*', '1')
			line = line.replace(' ', '0')
			result.append(line)
		dump = open('dump', 'w')
		return result
		

class WCLC(CLC):
	'''WCLC: A simple terminal UI for GoL using curses with wrapper and 
	showing only part of a larger grid.'''
	import curses
	g = LifeGrid()

	def __init__(self):
		self.g.rows = 200
		self.g.ranks = 200
		self.g.load("bigdefault")
		self.curses.wrapper(self.roundsLoop)

	def show(self, stdscr, prompt='', ul_x=0, ul_y=0, fieldOn='*', fieldOff=' '):
		'''show: A simple method for displaying the grid to stdout, which is 
		specialized for GoL. It allows the customization of what is printed 
		when a field is on or off when the method is invoked.'''
		winmaxy, winmaxx = stdscr.getmaxyx()
		winbegy, winbegx = stdscr.getbegyx()
		rows = winmaxy - winbegy - 1
		ranks = winmaxx - winbegx - 1
		for row in range(rows):
			stdscr.addstr(row, 0, '|')
			for rank in range(ranks - 1):
					if self.g.grid[ul_x + row][ul_y + rank] == self.g.live:
						stdscr.addstr(row, rank + 1, fieldOn)
					else:
						stdscr.addstr(row, rank + 1, fieldOff)
			stdscr.addstr(row, ranks, '|')
		stdscr.move(23, 0)
		stdscr.deleteln()
		stdscr.addstr(prompt)


class Ctest(object):
	import curses
	
	def __init__(self):
		self.curses.wrapper(self.main)
		
	def main(self, stdscr):
		while True:
			cmd = stdscr.getch()
			if cmd == (ord('q')):
				raise SystemExit
			elif cmd == (ord('s')):
				size = stdscr.getmaxyx()
				x, y = size
				stdscr.addstr(str(x))