#! /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))