1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
#!/usr/bin/env python
# life.py -- A curses-based version of Conway's Game of Life.
# Contributed by AMK
#
# An empty board will be displayed, and the following commands are available:
# E : Erase the board
# R : Fill the board randomly
# S : Step for a single generation
# C : Update continuously until a key is struck
# Q : Quit
# Cursor keys : Move the cursor around the board
# Space or Enter : Toggle the contents of the cursor's position
#
# TODO :
# Support the mouse
# Use colour if available
# Make board updates faster
#
import random, string, traceback
import curses
class LifeBoard:
"""Encapsulates a Life board
Attributes:
X,Y : horizontal and vertical size of the board
state : dictionary mapping (x,y) to 0 or 1
Methods:
display(update_board) -- If update_board is true, compute the
next generation. Then display the state
of the board and refresh the screen.
erase() -- clear the entire board
makeRandom() -- fill the board randomly
set(y,x) -- set the given cell to Live; doesn't refresh the screen
toggle(y,x) -- change the given cell from live to dead, or vice
versa, and refresh the screen display
"""
def __init__(self, scr, char=ord('*')):
"""Create a new LifeBoard instance.
scr -- curses screen object to use for display
char -- character used to render live cells (default: '*')
"""
self.state={} ; self.scr=scr
Y, X = self.scr.getmaxyx()
self.X, self.Y = X-2, Y-2-1
self.char = char
self.scr.clear()
# Draw a border around the board
border_line='+'+(self.X*'-')+'+'
self.scr.addstr(0, 0, border_line)
self.scr.addstr(self.Y+1,0, border_line)
for y in range(0, self.Y):
self.scr.addstr(1+y, 0, '|')
self.scr.addstr(1+y, self.X+1, '|')
self.scr.refresh()
def set(self, y, x):
"""Set a cell to the live state"""
if x<0 or self.X<=x or y<0 or self.Y<=y:
raise ValueError, "Coordinates out of range %i,%i"% (y,x)
self.state[x,y] = 1
def toggle(self, y, x):
"""Toggle a cell's state between live and dead"""
if x<0 or self.X<=x or y<0 or self.Y<=y:
raise ValueError, "Coordinates out of range %i,%i"% (y,x)
if self.state.has_key( (x,y) ):
del self.state[x,y]
self.scr.addch(y+1, x+1, ' ')
else:
self.state[x,y]=1
self.scr.addch(y+1, x+1, self.char)
self.scr.refresh()
def erase(self):
"""Clear the entire board and update the board display"""
self.state={}
self.display(update_board=0)
def display(self, update_board=1):
"""Display the whole board, optionally computing one generation"""
M,N = self.X, self.Y
if not update_board:
for i in range(0, M):
for j in range(0, N):
if self.state.has_key( (i,j) ):
self.scr.addch(j+1, i+1, self.char)
else:
self.scr.addch(j+1, i+1, ' ')
self.scr.refresh()
return
d={} ; self.boring=1
for i in range(0, M):
L=range( max(0, i-1), min(M, i+2) )
for j in range(0, N):
s=0
live=self.state.has_key( (i,j) )
for k in range( max(0, j-1), min(N, j+2) ):
for l in L:
if self.state.has_key( (l,k) ):
s=s+1
s=s-live
if s==3:
# Birth
d[i,j]=1
self.scr.addch(j+1, i+1, self.char)
if not live: self.boring=0
elif s==2 and live: d[i,j]=1 # Survival
elif live:
# Death
self.scr.addch(j+1, i+1, ' ')
self.boring=0
self.state=d
self.scr.refresh()
def makeRandom(self):
"Fill the board with a random pattern"
self.state={}
for i in range(0, self.X):
for j in range(0, self.Y):
if random.random() > 0.5: self.set(j,i)
def erase_menu(stdscr, menu_y):
"Clear the space where the menu resides"
stdscr.move(menu_y, 0) ; stdscr.clrtoeol()
stdscr.move(menu_y+1, 0) ; stdscr.clrtoeol()
def display_menu(stdscr, menu_y):
"Display the menu of possible keystroke commands"
erase_menu(stdscr, menu_y)
stdscr.addstr(menu_y, 4,
'Use the cursor keys to move, and space or Enter to toggle a cell.')
stdscr.addstr(menu_y+1, 4,
'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
def main(stdscr):
# Clear the screen and display the menu of keys
stdscr.clear()
stdscr_y, stdscr_x = stdscr.getmaxyx()
menu_y=(stdscr_y-3)-1
display_menu(stdscr, menu_y)
# Allocate a subwindow for the Life board and create the board object
subwin=stdscr.subwin(stdscr_y-3, stdscr_x, 0, 0)
board=LifeBoard(subwin, char=ord('*'))
board.display(update_board=0)
# xpos, ypos are the cursor's position
xpos, ypos = board.X/2, board.Y/2
# Main loop:
while (1):
stdscr.move(1+ypos, 1+xpos) # Move the cursor
c=stdscr.getch() # Get a keystroke
if 0<c<256:
c=chr(c)
if c in ' \n':
board.toggle(ypos, xpos)
elif c in 'Cc':
erase_menu(stdscr, menu_y)
stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously '
'updating the screen.')
stdscr.refresh()
# Activate nodelay mode; getch() will return -1
# if no keystroke is available, instead of waiting.
stdscr.nodelay(1)
while (1):
c=stdscr.getch()
if c!=-1: break
stdscr.addstr(0,0, '/'); stdscr.refresh()
board.display()
stdscr.addstr(0,0, '+'); stdscr.refresh()
stdscr.nodelay(0) # Disable nodelay mode
display_menu(stdscr, menu_y)
elif c in 'Ee': board.erase()
elif c in 'Qq': break
elif c in 'Rr':
board.makeRandom()
board.display(update_board=0)
elif c in 'Ss':
board.display()
else: pass # Ignore incorrect keys
elif c==curses.KEY_UP and ypos>0: ypos=ypos-1
elif c==curses.KEY_DOWN and ypos<board.Y-1: ypos=ypos+1
elif c==curses.KEY_LEFT and xpos>0: xpos=xpos-1
elif c==curses.KEY_RIGHT and xpos<board.X-1: xpos=xpos+1
else: pass # Ignore incorrect keys
if __name__=='__main__':
try:
# Initialize curses
stdscr=curses.initscr()
# Turn off echoing of keys, and enter cbreak mode,
# where no buffering is performed on keyboard input
curses.noecho() ; curses.cbreak()
# In keypad mode, escape sequences for special keys
# (like the cursor keys) will be interpreted and
# a special value like curses.KEY_LEFT will be returned
stdscr.keypad(1)
main(stdscr) # Enter the main loop
# Set everything back to normal
stdscr.keypad(0)
curses.echo() ; curses.nocbreak()
curses.endwin() # Terminate curses
except:
# In the event of an error, restore the terminal
# to a sane state.
stdscr.keypad(0)
curses.echo() ; curses.nocbreak()
curses.endwin()
traceback.print_exc() # Print the exception
|