ยปCore Development>Code coverage>Tools/demo/life.py

Python code coverage for Tools/demo/life.py

#countcontent
1n/a#!/usr/bin/env python3
2n/a
3n/a"""
4n/aA curses-based version of Conway's Game of Life.
5n/a
6n/aAn empty board will be displayed, and the following commands are available:
7n/a E : Erase the board
8n/a R : Fill the board randomly
9n/a S : Step for a single generation
10n/a C : Update continuously until a key is struck
11n/a Q : Quit
12n/a Cursor keys : Move the cursor around the board
13n/a Space or Enter : Toggle the contents of the cursor's position
14n/a
15n/aContributed by Andrew Kuchling, Mouse support and color by Dafydd Crosby.
16n/a"""
17n/a
18n/aimport curses
19n/aimport random
20n/a
21n/a
22n/aclass LifeBoard:
23n/a """Encapsulates a Life board
24n/a
25n/a Attributes:
26n/a X,Y : horizontal and vertical size of the board
27n/a state : dictionary mapping (x,y) to 0 or 1
28n/a
29n/a Methods:
30n/a display(update_board) -- If update_board is true, compute the
31n/a next generation. Then display the state
32n/a of the board and refresh the screen.
33n/a erase() -- clear the entire board
34n/a make_random() -- fill the board randomly
35n/a set(y,x) -- set the given cell to Live; doesn't refresh the screen
36n/a toggle(y,x) -- change the given cell from live to dead, or vice
37n/a versa, and refresh the screen display
38n/a
39n/a """
40n/a def __init__(self, scr, char=ord('*')):
41n/a """Create a new LifeBoard instance.
42n/a
43n/a scr -- curses screen object to use for display
44n/a char -- character used to render live cells (default: '*')
45n/a """
46n/a self.state = {}
47n/a self.scr = scr
48n/a Y, X = self.scr.getmaxyx()
49n/a self.X, self.Y = X - 2, Y - 2 - 1
50n/a self.char = char
51n/a self.scr.clear()
52n/a
53n/a # Draw a border around the board
54n/a border_line = '+' + (self.X * '-') + '+'
55n/a self.scr.addstr(0, 0, border_line)
56n/a self.scr.addstr(self.Y + 1, 0, border_line)
57n/a for y in range(0, self.Y):
58n/a self.scr.addstr(1 + y, 0, '|')
59n/a self.scr.addstr(1 + y, self.X + 1, '|')
60n/a self.scr.refresh()
61n/a
62n/a def set(self, y, x):
63n/a """Set a cell to the live state"""
64n/a if x < 0 or self.X <= x or y < 0 or self.Y <= y:
65n/a raise ValueError("Coordinates out of range %i,%i" % (y, x))
66n/a self.state[x, y] = 1
67n/a
68n/a def toggle(self, y, x):
69n/a """Toggle a cell's state between live and dead"""
70n/a if x < 0 or self.X <= x or y < 0 or self.Y <= y:
71n/a raise ValueError("Coordinates out of range %i,%i" % (y, x))
72n/a if (x, y) in self.state:
73n/a del self.state[x, y]
74n/a self.scr.addch(y + 1, x + 1, ' ')
75n/a else:
76n/a self.state[x, y] = 1
77n/a if curses.has_colors():
78n/a # Let's pick a random color!
79n/a self.scr.attrset(curses.color_pair(random.randrange(1, 7)))
80n/a self.scr.addch(y + 1, x + 1, self.char)
81n/a self.scr.attrset(0)
82n/a self.scr.refresh()
83n/a
84n/a def erase(self):
85n/a """Clear the entire board and update the board display"""
86n/a self.state = {}
87n/a self.display(update_board=False)
88n/a
89n/a def display(self, update_board=True):
90n/a """Display the whole board, optionally computing one generation"""
91n/a M, N = self.X, self.Y
92n/a if not update_board:
93n/a for i in range(0, M):
94n/a for j in range(0, N):
95n/a if (i, j) in self.state:
96n/a self.scr.addch(j + 1, i + 1, self.char)
97n/a else:
98n/a self.scr.addch(j + 1, i + 1, ' ')
99n/a self.scr.refresh()
100n/a return
101n/a
102n/a d = {}
103n/a self.boring = 1
104n/a for i in range(0, M):
105n/a L = range(max(0, i - 1), min(M, i + 2))
106n/a for j in range(0, N):
107n/a s = 0
108n/a live = (i, j) in self.state
109n/a for k in range(max(0, j - 1), min(N, j + 2)):
110n/a for l in L:
111n/a if (l, k) in self.state:
112n/a s += 1
113n/a s -= live
114n/a if s == 3:
115n/a # Birth
116n/a d[i, j] = 1
117n/a if curses.has_colors():
118n/a # Let's pick a random color!
119n/a self.scr.attrset(curses.color_pair(
120n/a random.randrange(1, 7)))
121n/a self.scr.addch(j + 1, i + 1, self.char)
122n/a self.scr.attrset(0)
123n/a if not live:
124n/a self.boring = 0
125n/a elif s == 2 and live:
126n/a # Survival
127n/a d[i, j] = 1
128n/a elif live:
129n/a # Death
130n/a self.scr.addch(j + 1, i + 1, ' ')
131n/a self.boring = 0
132n/a self.state = d
133n/a self.scr.refresh()
134n/a
135n/a def make_random(self):
136n/a "Fill the board with a random pattern"
137n/a self.state = {}
138n/a for i in range(0, self.X):
139n/a for j in range(0, self.Y):
140n/a if random.random() > 0.5:
141n/a self.set(j, i)
142n/a
143n/a
144n/adef erase_menu(stdscr, menu_y):
145n/a "Clear the space where the menu resides"
146n/a stdscr.move(menu_y, 0)
147n/a stdscr.clrtoeol()
148n/a stdscr.move(menu_y + 1, 0)
149n/a stdscr.clrtoeol()
150n/a
151n/a
152n/adef display_menu(stdscr, menu_y):
153n/a "Display the menu of possible keystroke commands"
154n/a erase_menu(stdscr, menu_y)
155n/a
156n/a # If color, then light the menu up :-)
157n/a if curses.has_colors():
158n/a stdscr.attrset(curses.color_pair(1))
159n/a stdscr.addstr(menu_y, 4,
160n/a 'Use the cursor keys to move, and space or Enter to toggle a cell.')
161n/a stdscr.addstr(menu_y + 1, 4,
162n/a 'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
163n/a stdscr.attrset(0)
164n/a
165n/a
166n/adef keyloop(stdscr):
167n/a # Clear the screen and display the menu of keys
168n/a stdscr.clear()
169n/a stdscr_y, stdscr_x = stdscr.getmaxyx()
170n/a menu_y = (stdscr_y - 3) - 1
171n/a display_menu(stdscr, menu_y)
172n/a
173n/a # If color, then initialize the color pairs
174n/a if curses.has_colors():
175n/a curses.init_pair(1, curses.COLOR_BLUE, 0)
176n/a curses.init_pair(2, curses.COLOR_CYAN, 0)
177n/a curses.init_pair(3, curses.COLOR_GREEN, 0)
178n/a curses.init_pair(4, curses.COLOR_MAGENTA, 0)
179n/a curses.init_pair(5, curses.COLOR_RED, 0)
180n/a curses.init_pair(6, curses.COLOR_YELLOW, 0)
181n/a curses.init_pair(7, curses.COLOR_WHITE, 0)
182n/a
183n/a # Set up the mask to listen for mouse events
184n/a curses.mousemask(curses.BUTTON1_CLICKED)
185n/a
186n/a # Allocate a subwindow for the Life board and create the board object
187n/a subwin = stdscr.subwin(stdscr_y - 3, stdscr_x, 0, 0)
188n/a board = LifeBoard(subwin, char=ord('*'))
189n/a board.display(update_board=False)
190n/a
191n/a # xpos, ypos are the cursor's position
192n/a xpos, ypos = board.X // 2, board.Y // 2
193n/a
194n/a # Main loop:
195n/a while True:
196n/a stdscr.move(1 + ypos, 1 + xpos) # Move the cursor
197n/a c = stdscr.getch() # Get a keystroke
198n/a if 0 < c < 256:
199n/a c = chr(c)
200n/a if c in ' \n':
201n/a board.toggle(ypos, xpos)
202n/a elif c in 'Cc':
203n/a erase_menu(stdscr, menu_y)
204n/a stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously '
205n/a 'updating the screen.')
206n/a stdscr.refresh()
207n/a # Activate nodelay mode; getch() will return -1
208n/a # if no keystroke is available, instead of waiting.
209n/a stdscr.nodelay(1)
210n/a while True:
211n/a c = stdscr.getch()
212n/a if c != -1:
213n/a break
214n/a stdscr.addstr(0, 0, '/')
215n/a stdscr.refresh()
216n/a board.display()
217n/a stdscr.addstr(0, 0, '+')
218n/a stdscr.refresh()
219n/a
220n/a stdscr.nodelay(0) # Disable nodelay mode
221n/a display_menu(stdscr, menu_y)
222n/a
223n/a elif c in 'Ee':
224n/a board.erase()
225n/a elif c in 'Qq':
226n/a break
227n/a elif c in 'Rr':
228n/a board.make_random()
229n/a board.display(update_board=False)
230n/a elif c in 'Ss':
231n/a board.display()
232n/a else:
233n/a # Ignore incorrect keys
234n/a pass
235n/a elif c == curses.KEY_UP and ypos > 0:
236n/a ypos -= 1
237n/a elif c == curses.KEY_DOWN and ypos + 1 < board.Y:
238n/a ypos += 1
239n/a elif c == curses.KEY_LEFT and xpos > 0:
240n/a xpos -= 1
241n/a elif c == curses.KEY_RIGHT and xpos + 1 < board.X:
242n/a xpos += 1
243n/a elif c == curses.KEY_MOUSE:
244n/a mouse_id, mouse_x, mouse_y, mouse_z, button_state = curses.getmouse()
245n/a if (mouse_x > 0 and mouse_x < board.X + 1 and
246n/a mouse_y > 0 and mouse_y < board.Y + 1):
247n/a xpos = mouse_x - 1
248n/a ypos = mouse_y - 1
249n/a board.toggle(ypos, xpos)
250n/a else:
251n/a # They've clicked outside the board
252n/a curses.flash()
253n/a else:
254n/a # Ignore incorrect keys
255n/a pass
256n/a
257n/a
258n/adef main(stdscr):
259n/a keyloop(stdscr) # Enter the main loop
260n/a
261n/aif __name__ == '__main__':
262n/a curses.wrapper(main)