2 # life.py -- A curses-based version of Conway's Game of Life.
3 # Contributed by A.M. Kuchling <amk1@bigfoot.com>
5 # An empty board will be displayed, and the following commands are available:
7 # R : Fill the board randomly
8 # S : Step for a single generation
9 # C : Update continuously until a key is struck
11 # Cursor keys : Move the cursor around the board
12 # Space or Enter : Toggle the contents of the cursor's position
16 # Use colour if available
17 # Make board updates faster
21 """Encapsulates a Life board
24 X,Y : horizontal and vertical size of the board
25 state : dictionary mapping (x,y) to 0 or 1
28 display(update_board) -- If update_board is true, compute the
29 next generation. Then display the state
30 of the board and refresh the screen.
31 erase() -- clear the entire board
32 makeRandom() -- fill the board randomly
33 set(y,x) -- set the given cell to Live; doesn't refresh the screen
34 toggle(y,x) -- change the given cell from live to dead, or vice
35 versa, and refresh the screen display
38 def __init__(self
, scr
, char
=ord('*')):
39 """Create a new LifeBoard instance.
41 scr -- curses screen object to use for display
42 char -- character used to render live cells (default: '*')
44 self
.state
={} ; self
.scr
=scr
45 Y
, X
= self
.scr
.getmaxyx()
46 self
.X
, self
.Y
= X
-2, Y
-2-1
50 # Draw a border around the board
51 border_line
='+'+(self
.X
*'-')+'+'
52 self
.scr
.addstr(0, 0, border_line
)
53 self
.scr
.addstr(self
.Y
+1,0, border_line
)
54 for y
in range(0, self
.Y
):
55 self
.scr
.addstr(1+y
, 0, '|')
56 self
.scr
.addstr(1+y
, self
.X
+1, '|')
60 """Set a cell to the live state"""
61 if x
<0 or self
.X
<=x
or y
<0 or self
.Y
<=y
:
62 raise ValueError, "Coordinates out of range %i,%i"% (y
,x
)
65 def toggle(self
, y
, x
):
66 """Toggle a cell's state between live and dead"""
67 if x
<0 or self
.X
<=x
or y
<0 or self
.Y
<=y
:
68 raise ValueError, "Coordinates out of range %i,%i"% (y
,x
)
69 if self
.state
.has_key( (x
,y
) ):
71 self
.scr
.addch(y
+1, x
+1, ' ')
74 self
.scr
.addch(y
+1, x
+1, self
.char
)
78 """Clear the entire board and update the board display"""
80 self
.display(update_board
=0)
82 def display(self
, update_board
=1):
83 """Display the whole board, optionally computing one generation"""
88 if self
.state
.has_key( (i
,j
) ):
89 self
.scr
.addch(j
+1, i
+1, self
.char
)
91 self
.scr
.addch(j
+1, i
+1, ' ')
97 L
=range( max(0, i
-1), min(M
, i
+2) )
100 live
=self
.state
.has_key( (i
,j
) )
101 for k
in range( max(0, j
-1), min(N
, j
+2) ):
103 if self
.state
.has_key( (l
,k
) ):
109 self
.scr
.addch(j
+1, i
+1, self
.char
)
110 if not live
: self
.boring
=0
111 elif s
==2 and live
: d
[i
,j
]=1 # Survival
114 self
.scr
.addch(j
+1, i
+1, ' ')
119 def makeRandom(self
):
120 "Fill the board with a random pattern"
123 for i
in range(0, self
.X
):
124 for j
in range(0, self
.Y
):
125 if whrandom
.random()*10>5.0: self
.set(j
,i
)
128 def erase_menu(stdscr
, menu_y
):
129 "Clear the space where the menu resides"
130 stdscr
.move(menu_y
, 0) ; stdscr
.clrtoeol()
131 stdscr
.move(menu_y
+1, 0) ; stdscr
.clrtoeol()
133 def display_menu(stdscr
, menu_y
):
134 "Display the menu of possible keystroke commands"
135 erase_menu(stdscr
, menu_y
)
136 stdscr
.addstr(menu_y
, 4,
137 'Use the cursor keys to move, and space or Enter to toggle a cell.')
138 stdscr
.addstr(menu_y
+1, 4,
139 'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
142 import string
, curses
144 # Clear the screen and display the menu of keys
146 stdscr_y
, stdscr_x
= stdscr
.getmaxyx()
147 menu_y
=(stdscr_y
-3)-1
148 display_menu(stdscr
, menu_y
)
150 # Allocate a subwindow for the Life board and create the board object
151 subwin
=stdscr
.subwin(stdscr_y
-3, stdscr_x
, 0, 0)
152 board
=LifeBoard(subwin
, char
=ord('*'))
153 board
.display(update_board
=0)
155 # xpos, ypos are the cursor's position
156 xpos
, ypos
= board
.X
/2, board
.Y
/2
160 stdscr
.move(1+ypos
, 1+xpos
) # Move the cursor
161 c
=stdscr
.getch() # Get a keystroke
165 board
.toggle(ypos
, xpos
)
167 erase_menu(stdscr
, menu_y
)
168 stdscr
.addstr(menu_y
, 6, ' Hit any key to stop continuously '
169 'updating the screen.')
171 # Activate nodelay mode; getch() will return -1
172 # if no keystroke is available, instead of waiting.
177 stdscr
.addstr(0,0, '/'); stdscr
.refresh()
179 stdscr
.addstr(0,0, '+'); stdscr
.refresh()
181 stdscr
.nodelay(0) # Disable nodelay mode
182 display_menu(stdscr
, menu_y
)
184 elif c
in 'Ee': board
.erase()
185 elif c
in 'Qq': break
188 board
.display(update_board
=0)
191 else: pass # Ignore incorrect keys
192 elif c
==curses
.KEY_UP
and ypos
>0: ypos
=ypos
-1
193 elif c
==curses
.KEY_DOWN
and ypos
<board
.Y
-1: ypos
=ypos
+1
194 elif c
==curses
.KEY_LEFT
and xpos
>0: xpos
=xpos
-1
195 elif c
==curses
.KEY_RIGHT
and xpos
<board
.X
-1: xpos
=xpos
+1
196 else: pass # Ignore incorrect keys
198 if __name__
=='__main__':
199 import curses
, traceback
202 stdscr
=curses
.initscr()
203 # Turn off echoing of keys, and enter cbreak mode,
204 # where no buffering is performed on keyboard input
205 curses
.noecho() ; curses
.cbreak()
207 # In keypad mode, escape sequences for special keys
208 # (like the cursor keys) will be interpreted and
209 # a special value like curses.KEY_LEFT will be returned
211 main(stdscr
) # Enter the main loop
212 # Set everything back to normal
214 curses
.echo() ; curses
.nocbreak()
215 curses
.endwin() # Terminate curses
217 # In the event of an error, restore the terminal
220 curses
.echo() ; curses
.nocbreak()
222 traceback
.print_exc() # Print the exception