When you move a pen point around the screen with the WASD keys, the etching drawer forms a picture by tracing a continuous line, like the Etch A Sketch toy. Let your artistic side break out and see what images you can create! This program also lets you save your drawings to a text file so you can print them out later. Plus, you can copy and paste the WASD movements of other drawings into this program, like the WASD commands for the Hilbert Curve fractal presented on lines 6 to 14 of the source code.
When you run etchingdrawer.py, the output will look like Figure 23-1.
Like Project 17, “Dice Math,” this program uses a dictionary stored in a variable named canvas
to record the lines of the drawing. The keys are (x, y)
tuples and the values are strings representing the line shape to draw at those x, y coordinates on the screen. A full list of Unicode characters you can use in your Python programs is given in Appendix B.
Line 126 has a function call to open()
that passes an encoding='utf-8'
argument. The reasons are beyond the scope of this book, but this is necessary for Windows to write the line characters to a text file.
1. """Etching Drawer, by Al Sweigart [email protected]
2. An art program that draws a continuous line around the screen using the
3. WASD keys. Inspired by Etch A Sketch toys.
4.
5. For example, you can draw Hilbert Curve fractal with:
6. SDWDDSASDSAAWASSDSASSDWDSDWWAWDDDSASSDWDSDWWAWDWWASAAWDWAWDDSDW
7.
8. Or an even larger Hilbert Curve fractal with:
9. DDSAASSDDWDDSDDWWAAWDDDDSDDWDDDDSAASDDSAAAAWAASSSDDWDDDDSAASDDSAAAAWA
10. ASAAAAWDDWWAASAAWAASSDDSAASSDDWDDDDSAASDDSAAAAWAASSDDSAASSDDWDDSDDWWA
11. AWDDDDDDSAASSDDWDDSDDWWAAWDDWWAASAAAAWDDWAAWDDDDSDDWDDSDDWDDDDSAASDDS
12. AAAAWAASSDDSAASSDDWDDSDDWWAAWDDDDDDSAASSDDWDDSDDWWAAWDDWWAASAAAAWDDWA
13. AWDDDDSDDWWAAWDDWWAASAAWAASSDDSAAAAWAASAAAAWDDWAAWDDDDSDDWWWAASAAAAWD
14. DWAAWDDDDSDDWDDDDSAASSDDWDDSDDWWAAWDD
15.
16. This code is available at https://nostarch.com/big-book-small-python-programming
17. Tags: large, artistic"""
18.
19. import shutil, sys
20.
21. # Set up the constants for line characters:
22. UP_DOWN_CHAR = chr(9474) # Character 9474 is '│'
23. LEFT_RIGHT_CHAR = chr(9472) # Character 9472 is '─'
24. DOWN_RIGHT_CHAR = chr(9484) # Character 9484 is '┌'
25. DOWN_LEFT_CHAR = chr(9488) # Character 9488 is '┐'
26. UP_RIGHT_CHAR = chr(9492) # Character 9492 is '└'
27. UP_LEFT_CHAR = chr(9496) # Character 9496 is '┘'
28. UP_DOWN_RIGHT_CHAR = chr(9500) # Character 9500 is '├'
29. UP_DOWN_LEFT_CHAR = chr(9508) # Character 9508 is '┤'
30. DOWN_LEFT_RIGHT_CHAR = chr(9516) # Character 9516 is '┬'
31. UP_LEFT_RIGHT_CHAR = chr(9524) # Character 9524 is '┴'
32. CROSS_CHAR = chr(9532) # Character 9532 is '┼'
33. # A list of chr() codes is at https://inventwithpython.com/chr
34.
35. # Get the size of the terminal window:
36. CANVAS_WIDTH, CANVAS_HEIGHT = shutil.get_terminal_size()
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. CANVAS_WIDTH -= 1
40. # Leave room at the bottom few rows for the command info lines.
41. CANVAS_HEIGHT -= 5
42.
43. """The keys for canvas will be (x, y) integer tuples for the coordinate,
44. and the value is a set of letters W, A, S, D that tell what kind of line
45. should be drawn."""
46. canvas = {}
47. cursorX = 0
48. cursorY = 0
49.
50.
51. def getCanvasString(canvasData, cx, cy):
52. """Returns a multiline string of the line drawn in canvasData."""
53. canvasStr = ''
54.
55. """canvasData is a dictionary with (x, y) tuple keys and values that
56. are sets of 'W', 'A', 'S', and/or 'D' strings to show which
57. directions the lines are drawn at each xy point."""
58. for rowNum in range(CANVAS_HEIGHT):
59. for columnNum in range(CANVAS_WIDTH):
60. if columnNum == cx and rowNum == cy:
61. canvasStr += '#'
62. continue
63.
64. # Add the line character for this point to canvasStr.
65. cell = canvasData.get((columnNum, rowNum))
66. if cell in (set(['W', 'S']), set(['W']), set(['S'])):
67. canvasStr += UP_DOWN_CHAR
68. elif cell in (set(['A', 'D']), set(['A']), set(['D'])):
69. canvasStr += LEFT_RIGHT_CHAR
70. elif cell == set(['S', 'D']):
71. canvasStr += DOWN_RIGHT_CHAR
72. elif cell == set(['A', 'S']):
73. canvasStr += DOWN_LEFT_CHAR
74. elif cell == set(['W', 'D']):
75. canvasStr += UP_RIGHT_CHAR
76. elif cell == set(['W', 'A']):
77. canvasStr += UP_LEFT_CHAR
78. elif cell == set(['W', 'S', 'D']):
79. canvasStr += UP_DOWN_RIGHT_CHAR
80. elif cell == set(['W', 'S', 'A']):
81. canvasStr += UP_DOWN_LEFT_CHAR
82. elif cell == set(['A', 'S', 'D']):
83. canvasStr += DOWN_LEFT_RIGHT_CHAR
84. elif cell == set(['W', 'A', 'D']):
85. canvasStr += UP_LEFT_RIGHT_CHAR
86. elif cell == set(['W', 'A', 'S', 'D']):
87. canvasStr += CROSS_CHAR
88. elif cell == None:
89. canvasStr += ' '
90. canvasStr += '\n' # Add a newline at the end of each row.
91. return canvasStr
92.
93.
94. moves = []
95. while True: # Main program loop.
96. # Draw the lines based on the data in canvas:
97. print(getCanvasString(canvas, cursorX, cursorY))
98.
99. print('WASD keys to move, H for help, C to clear, '
100. + 'F to save, or QUIT.')
101. response = input('> ').upper()
102.
103. if response == 'QUIT':
104. print('Thanks for playing!')
105. sys.exit() # Quit the program.
106. elif response == 'H':
107. print('Enter W, A, S, and D characters to move the cursor and')
108. print('draw a line behind it as it moves. For example, ddd')
109. print('draws a line going right and sssdddwwwaaa draws a box.')
110. print()
111. print('You can save your drawing to a text file by entering F.')
112. input('Press Enter to return to the program...')
113. continue
114. elif response == 'C':
115. canvas = {} # Erase the canvas data.
116. moves.append('C') # Record this move.
117. elif response == 'F':
118. # Save the canvas string to a text file:
119. try:
120. print('Enter filename to save to:')
121. filename = input('> ')
122.
123. # Make sure the filename ends with .txt:
124. if not filename.endswith('.txt'):
125. filename += '.txt'
126. with open(filename, 'w', encoding='utf-8') as file:
127. file.write(''.join(moves) + '\n')
128. file.write(getCanvasString(canvas, None, None))
129. except:
130. print('ERROR: Could not save file.')
131.
132. for command in response:
133. if command not in ('W', 'A', 'S', 'D'):
134. continue # Ignore this letter and continue to the next one.
135. moves.append(command) # Record this move.
136.
137. # The first line we add needs to form a full line:
138. if canvas == {}:
139. if command in ('W', 'S'):
140. # Make the first line a horizontal one:
141. canvas[(cursorX, cursorY)] = set(['W', 'S'])
142. elif command in ('A', 'D'):
143. # Make the first line a vertical one:
144. canvas[(cursorX, cursorY)] = set(['A', 'D'])
145.
146. # Update x and y:
147. if command == 'W' and cursorY > 0:
148. canvas[(cursorX, cursorY)].add(command)
149. cursorY = cursorY - 1
150. elif command == 'S' and cursorY < CANVAS_HEIGHT - 1:
151. canvas[(cursorX, cursorY)].add(command)
152. cursorY = cursorY + 1
153. elif command == 'A' and cursorX > 0:
154. canvas[(cursorX, cursorY)].add(command)
155. cursorX = cursorX - 1
156. elif command == 'D' and cursorX < CANVAS_WIDTH - 1:
157. canvas[(cursorX, cursorY)].add(command)
158. cursorX = cursorX + 1
159. else:
160. # If the cursor doesn't move because it would have moved off
161. # the edge of the canvas, then don't change the set at
162. # canvas[(cursorX, cursorY)].
163. continue
164.
165. # If there's no set for (cursorX, cursorY), add an empty set:
166. if (cursorX, cursorY) not in canvas:
167. canvas[(cursorX, cursorY)] = set()
168.
169. # Add the direction string to this xy point's set:
170. if command == 'W':
171. canvas[(cursorX, cursorY)].add('S')
172. elif command == 'S':
173. canvas[(cursorX, cursorY)].add('W')
174. elif command == 'A':
175. canvas[(cursorX, cursorY)].add('D')
176. elif command == 'D':
177. canvas[(cursorX, cursorY)].add('A')
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.
response = input('> ').upper()
on line 101 to response = input('> ')
?canvasStr += '#'
on line 61 to canvasStr += '@'
?canvasStr += ' '
on line 89 to canvasStr += '.'
?moves = []
on line 94 to moves = list('SDWDDSASDSAAWASSDSAS')
?