2 # life.py -- A curses-based version of Conway's Game of Life.
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
20 import random
, string
, traceback
24 """Encapsulates a Life board
27 X,Y : horizontal and vertical size of the board
28 state : dictionary mapping (x,y) to 0 or 1
31 display(update_board) -- If update_board is true, compute the
32 next generation. Then display the state
33 of the board and refresh the screen.
34 erase() -- clear the entire board
35 makeRandom() -- fill the board randomly
36 set(y,x) -- set the given cell to Live; doesn't refresh the screen
37 toggle(y,x) -- change the given cell from live to dead, or vice
38 versa, and refresh the screen display
41 def __init__(self
, scr
, char
=ord('*')):
42 """Create a new LifeBoard instance.
44 scr -- curses screen object to use for display
45 char -- character used to render live cells (default: '*')
49 Y
, X
= self
.scr
.getmaxyx()
50 self
.X
, self
.Y
= X
-2, Y
-2-1
54 # Draw a border around the board
55 border_line
= '+'+(self
.X
*'-')+'+'
56 self
.scr
.addstr(0, 0, border_line
)
57 self
.scr
.addstr(self
.Y
+1,0, border_line
)
58 for y
in range(0, self
.Y
):
59 self
.scr
.addstr(1+y
, 0, '|')
60 self
.scr
.addstr(1+y
, self
.X
+1, '|')
64 """Set a cell to the live state"""
65 if x
<0 or self
.X
<=x
or y
<0 or self
.Y
<=y
:
66 raise ValueError, "Coordinates out of range %i,%i"% (y
,x
)
69 def toggle(self
, y
, x
):
70 """Toggle a cell's state between live and dead"""
71 if x
<0 or self
.X
<=x
or y
<0 or self
.Y
<=y
:
72 raise ValueError, "Coordinates out of range %i,%i"% (y
,x
)
73 if self
.state
.has_key( (x
,y
) ):
75 self
.scr
.addch(y
+1, x
+1, ' ')
78 self
.scr
.addch(y
+1, x
+1, self
.char
)
82 """Clear the entire board and update the board display"""
84 self
.display(update_board
=False)
86 def display(self
, update_board
=True):
87 """Display the whole board, optionally computing one generation"""
92 if self
.state
.has_key( (i
,j
) ):
93 self
.scr
.addch(j
+1, i
+1, self
.char
)
95 self
.scr
.addch(j
+1, i
+1, ' ')
101 for i
in range(0, M
):
102 L
= range( max(0, i
-1), min(M
, i
+2) )
103 for j
in range(0, N
):
105 live
= self
.state
.has_key( (i
,j
) )
106 for k
in range( max(0, j
-1), min(N
, j
+2) ):
108 if self
.state
.has_key( (l
,k
) ):
114 self
.scr
.addch(j
+1, i
+1, self
.char
)
115 if not live
: self
.boring
= 0
116 elif s
== 2 and live
: d
[i
,j
] = 1 # Survival
119 self
.scr
.addch(j
+1, i
+1, ' ')
124 def makeRandom(self
):
125 "Fill the board with a random pattern"
127 for i
in range(0, self
.X
):
128 for j
in range(0, self
.Y
):
129 if random
.random() > 0.5:
133 def erase_menu(stdscr
, menu_y
):
134 "Clear the space where the menu resides"
135 stdscr
.move(menu_y
, 0)
137 stdscr
.move(menu_y
+1, 0)
140 def display_menu(stdscr
, menu_y
):
141 "Display the menu of possible keystroke commands"
142 erase_menu(stdscr
, menu_y
)
143 stdscr
.addstr(menu_y
, 4,
144 'Use the cursor keys to move, and space or Enter to toggle a cell.')
145 stdscr
.addstr(menu_y
+1, 4,
146 'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
149 # Clear the screen and display the menu of keys
151 stdscr_y
, stdscr_x
= stdscr
.getmaxyx()
152 menu_y
= (stdscr_y
-3)-1
153 display_menu(stdscr
, menu_y
)
155 # Allocate a subwindow for the Life board and create the board object
156 subwin
= stdscr
.subwin(stdscr_y
-3, stdscr_x
, 0, 0)
157 board
= LifeBoard(subwin
, char
=ord('*'))
158 board
.display(update_board
=False)
160 # xpos, ypos are the cursor's position
161 xpos
, ypos
= board
.X
//2, board
.Y
//2
165 stdscr
.move(1+ypos
, 1+xpos
) # Move the cursor
166 c
= stdscr
.getch() # Get a keystroke
170 board
.toggle(ypos
, xpos
)
172 erase_menu(stdscr
, menu_y
)
173 stdscr
.addstr(menu_y
, 6, ' Hit any key to stop continuously '
174 'updating the screen.')
176 # Activate nodelay mode; getch() will return -1
177 # if no keystroke is available, instead of waiting.
183 stdscr
.addstr(0,0, '/')
186 stdscr
.addstr(0,0, '+')
189 stdscr
.nodelay(0) # Disable nodelay mode
190 display_menu(stdscr
, menu_y
)
198 board
.display(update_board
=False)
201 else: pass # Ignore incorrect keys
202 elif c
== curses
.KEY_UP
and ypos
>0: ypos
-= 1
203 elif c
== curses
.KEY_DOWN
and ypos
<board
.Y
-1: ypos
+= 1
204 elif c
== curses
.KEY_LEFT
and xpos
>0: xpos
-= 1
205 elif c
== curses
.KEY_RIGHT
and xpos
<board
.X
-1: xpos
+= 1
207 # Ignore incorrect keys
212 keyloop(stdscr
) # Enter the main loop
215 if __name__
== '__main__':