This program creates a scrolling field of ducklings. Each duckling has slight variations: they can face left or right and have two different body sizes, four types of eyes, two types of mouths, and three positions for their wings. This gives us 96 different possible variations, which the Ducklings program produces endlessly.
When you run ducklings.py, the output will look like this:
Duckling Screensaver, by Al Sweigart [email protected]
Press Ctrl-C to quit...
=" )
=") ( v)=")
( ^) ^ ^ ( v) >'')
^^ ^^ ( ^)
>") ^ ^
( v) =^^)
("< ("< >") ^^ ( >)
(^ ) (< ) ( ^) ^ ^
^^ ^^ ("< ^^ (``<>^^)
(^^= (^ ) (< )( ^)
(v ) ( "< ^^ ^ ^ ^ ^
--snip--
This program represents ducklings with a Duckling
class. The random features of each ducking are chosen in the __init__()
method of this class, while the various body parts of each duckling are returned by the getHeadStr()
, getBodyStr()
, and getFeetStr()
methods.
1. """Duckling Screensaver, by Al Sweigart [email protected]
2. A screensaver of many many ducklings.
3.
4. >" ) =^^) (``= ("= >") ("=
5. ( >) ( ^) (v ) (^ ) ( >) (v )
6. ^ ^ ^ ^ ^ ^ ^^ ^^ ^^
7.
8. This code is available at https://nostarch.com/big-book-small-python-programming
9. Tags: large, artistic, object-oriented, scrolling"""
10.
11. import random, shutil, sys, time
12.
13. # Set up the constants:
14. PAUSE = 0.2 # (!) Try changing this to 1.0 or 0.0.
15. DENSITY = 0.10 # (!) Try changing this to anything from 0.0 to 1.0.
16.
17. DUCKLING_WIDTH = 5
18. LEFT = 'left'
19. RIGHT = 'right'
20. BEADY = 'beady'
21. WIDE = 'wide'
22. HAPPY = 'happy'
23. ALOOF = 'aloof'
24. CHUBBY = 'chubby'
25. VERY_CHUBBY = 'very chubby'
26. OPEN = 'open'
27. CLOSED = 'closed'
28. OUT = 'out'
29. DOWN = 'down'
30. UP = 'up'
31. HEAD = 'head'
32. BODY = 'body'
33. FEET = 'feet'
34.
35. # Get the size of the terminal window:
36. WIDTH = shutil.get_terminal_size()[0]
37. # We can't print to the last column on Windows without it adding a
38. # newline automatically, so reduce the width by one:
39. WIDTH -= 1
40.
41.
42. def main():
43. print('Duckling Screensaver, by Al Sweigart')
44. print('Press Ctrl-C to quit...')
45. time.sleep(2)
46.
47. ducklingLanes = [None] * (WIDTH // DUCKLING_WIDTH)
48.
49. while True: # Main program loop.
50. for laneNum, ducklingObj in enumerate(ducklingLanes):
51. # See if we should create a duckling in this lane:
52. if (ducklingObj == None and random.random() <= DENSITY):
53. # Place a duckling in this lane:
54. ducklingObj = Duckling()
55. ducklingLanes[laneNum] = ducklingObj
56.
57. if ducklingObj != None:
58. # Draw a duckling if there is one in this lane:
59. print(ducklingObj.getNextBodyPart(), end='')
60. # Delete the duckling if we've finished drawing it:
61. if ducklingObj.partToDisplayNext == None:
62. ducklingLanes[laneNum] = None
63. else:
64. # Draw five spaces since there is no duckling here.
65. print(' ' * DUCKLING_WIDTH, end='')
66.
67. print() # Print a newline.
68. sys.stdout.flush() # Make sure text appears on the screen.
69. time.sleep(PAUSE)
70.
71.
72. class Duckling:
73. def __init__(self):
74. """Create a new duckling with random body features."""
75. self.direction = random.choice([LEFT, RIGHT])
76. self.body = random.choice([CHUBBY, VERY_CHUBBY])
77. self.mouth = random.choice([OPEN, CLOSED])
78. self.wing = random.choice([OUT, UP, DOWN])
79.
80. if self.body == CHUBBY:
81. # Chubby ducklings can only have beady eyes.
82. self.eyes = BEADY
83. else:
84. self.eyes = random.choice([BEADY, WIDE, HAPPY, ALOOF])
85.
86. self.partToDisplayNext = HEAD
87.
88. def getHeadStr(self):
89. """Returns the string of the duckling's head."""
90. headStr = ''
91. if self.direction == LEFT:
92. # Get the mouth:
93. if self.mouth == OPEN:
94. headStr += '>'
95. elif self.mouth == CLOSED:
96. headStr += '='
97.
98. # Get the eyes:
99. if self.eyes == BEADY and self.body == CHUBBY:
100. headStr += '"'
101. elif self.eyes == BEADY and self.body == VERY_CHUBBY:
102. headStr += '" '
103. elif self.eyes == WIDE:
104. headStr += "''"
105. elif self.eyes == HAPPY:
106. headStr += '^^'
107. elif self.eyes == ALOOF:
108. headStr += '``'
109.
110. headStr += ') ' # Get the back of the head.
111.
112. if self.direction == RIGHT:
113. headStr += ' (' # Get the back of the head.
114.
115. # Get the eyes:
116. if self.eyes == BEADY and self.body == CHUBBY:
117. headStr += '"'
118. elif self.eyes == BEADY and self.body == VERY_CHUBBY:
119. headStr += ' "'
120. elif self.eyes == WIDE:
121. headStr += "''"
122. elif self.eyes == HAPPY:
123. headStr += '^^'
124. elif self.eyes == ALOOF:
125. headStr += '``'
126.
127. # Get the mouth:
128. if self.mouth == OPEN:
129. headStr += '<'
130. elif self.mouth == CLOSED:
131. headStr += '='
132.
133. if self.body == CHUBBY:
134. # Get an extra space so chubby ducklings are the same
135. # width as very chubby ducklings.
136. headStr += ' '
137.
138. return headStr
139.
140. def getBodyStr(self):
141. """Returns the string of the duckling's body."""
142. bodyStr = '(' # Get the left side of the body.
143. if self.direction == LEFT:
144. # Get the interior body space:
145. if self.body == CHUBBY:
146. bodyStr += ' '
147. elif self.body == VERY_CHUBBY:
148. bodyStr += ' '
149.
150. # Get the wing:
151. if self.wing == OUT:
152. bodyStr += '>'
153. elif self.wing == UP:
154. bodyStr += '^'
155. elif self.wing == DOWN:
156. bodyStr += 'v'
157.
158. if self.direction == RIGHT:
159. # Get the wing:
160. if self.wing == OUT:
161. bodyStr += '<'
162. elif self.wing == UP:
163. bodyStr += '^'
164. elif self.wing == DOWN:
165. bodyStr += 'v'
166.
167. # Get the interior body space:
168. if self.body == CHUBBY:
169. bodyStr += ' '
170. elif self.body == VERY_CHUBBY:
171. bodyStr += ' '
172.
173. bodyStr += ')' # Get the right side of the body.
174.
175. if self.body == CHUBBY:
176. # Get an extra space so chubby ducklings are the same
177. # width as very chubby ducklings.
178. bodyStr += ' '
179.
180. return bodyStr
181.
182. def getFeetStr(self):
183. """Returns the string of the duckling's feet."""
184. if self.body == CHUBBY:
185. return ' ^^ '
186. elif self.body == VERY_CHUBBY:
187. return ' ^ ^ '
188.
189. def getNextBodyPart(self):
190. """Calls the appropriate display method for the next body
191. part that needs to be displayed. Sets partToDisplayNext to
192. None when finished."""
193. if self.partToDisplayNext == HEAD:
194. self.partToDisplayNext = BODY
195. return self.getHeadStr()
196. elif self.partToDisplayNext == BODY:
197. self.partToDisplayNext = FEET
198. return self.getBodyStr()
199. elif self.partToDisplayNext == FEET:
200. self.partToDisplayNext = None
201. return self.getFeetStr()
202.
203.
204.
205. # If this program was run (instead of imported), run the game:
206. if __name__ == '__main__':
207. try:
208. main()
209. except KeyboardInterrupt:
210. sys.exit() # When Ctrl-C is pressed, end the program.
After entering the source code and running it a few times, try making experimental changes to it. The comments marked with (!)
have suggestions for small changes you can make.
Try to find the answers to the following questions. Experiment with some modifications to the code and rerun the program to see what effect the changes have.
random.choice([LEFT, RIGHT])
on line 75 to random.choice([LEFT])?
self.partToDisplayNext = BODY
on line 194 to self.partToDisplayNext = None
?self.partToDisplayNext = FEET
on line 197 to self.partToDisplayNext = BODY
?return
self.getHeadStr()
on line 195 to return
self.getFeetStr()
?