- Notifications
You must be signed in to change notification settings - Fork 31.7k
/
Copy pathlife.py
executable file
·216 lines (190 loc) · 7.18 KB
/
life.py
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
#!/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
#
importrandom, string, traceback
importcurses
classLifeBoard:
"""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)
foryinrange(0, self.Y):
self.scr.addstr(1+y, 0, '|')
self.scr.addstr(1+y, self.X+1, '|')
self.scr.refresh()
defset(self, y, x):
"""Set a cell to the live state"""
ifx<0orself.X<=xory<0orself.Y<=y:
raiseValueError, "Coordinates out of range %i,%i"% (y,x)
self.state[x,y] =1
deftoggle(self, y, x):
"""Toggle a cell's state between live and dead"""
ifx<0orself.X<=xory<0orself.Y<=y:
raiseValueError, "Coordinates out of range %i,%i"% (y,x)
ifself.state.has_key( (x,y) ):
delself.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()
deferase(self):
"""Clear the entire board and update the board display"""
self.state= {}
self.display(update_board=False)
defdisplay(self, update_board=True):
"""Display the whole board, optionally computing one generation"""
M,N=self.X, self.Y
ifnotupdate_board:
foriinrange(0, M):
forjinrange(0, N):
ifself.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
foriinrange(0, M):
L=range( max(0, i-1), min(M, i+2) )
forjinrange(0, N):
s=0
live=self.state.has_key( (i,j) )
forkinrange( max(0, j-1), min(N, j+2) ):
forlinL:
ifself.state.has_key( (l,k) ):
s+=1
s-=live
ifs==3:
# Birth
d[i,j] =1
self.scr.addch(j+1, i+1, self.char)
ifnotlive: self.boring=0
elifs==2andlive: d[i,j] =1# Survival
eliflive:
# Death
self.scr.addch(j+1, i+1, ' ')
self.boring=0
self.state=d
self.scr.refresh()
defmakeRandom(self):
"Fill the board with a random pattern"
self.state= {}
foriinrange(0, self.X):
forjinrange(0, self.Y):
ifrandom.random() >0.5:
self.set(j,i)
deferase_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()
defdisplay_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')
defkeyloop(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=False)
# 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
if0<c<256:
c=chr(c)
ifcin' \n':
board.toggle(ypos, xpos)
elifcin'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()
ifc!=-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)
elifcin'Ee':
board.erase()
elifcin'Qq':
break
elifcin'Rr':
board.makeRandom()
board.display(update_board=False)
elifcin'Ss':
board.display()
else: pass# Ignore incorrect keys
elifc==curses.KEY_UPandypos>0: ypos-=1
elifc==curses.KEY_DOWNandypos<board.Y-1: ypos+=1
elifc==curses.KEY_LEFTandxpos>0: xpos-=1
elifc==curses.KEY_RIGHTandxpos<board.X-1: xpos+=1
else:
# Ignore incorrect keys
pass
defmain(stdscr):
keyloop(stdscr) # Enter the main loop
if__name__=='__main__':
curses.wrapper(main)